Spring Securityのお話
ハーイ!僕です。
最近、Spring Securityと戦っています。今のプロジェクトでセキュリティまわりを僕が担当しているのでね。
Spring Frameworkを勉強しはじめて、最初につまづいたのがSpring Securityです。いやね、これね、ちょっと僕には難しすぎるんじゃない?
「人類には早すぎたのだ!!*1」
と、突然机の前で発狂しながら仕事してます。周りの人はびっくりさせてごめんなさい。
これ以上暴れてまわりの人から変人扱いされても困るので、自分なりにSpring Securityについて纏めてみようと思います。
SpringSecurityを理解する上では、
- Filterのお話
- Configのお話
- Configurationのお話
がそれぞれあると思っているので、その順番で〜
Filterのお話
Spring Securityは主にサーブレットフィルターをもちいてWebアプリケーションのセキュリティ機能を実現しています。
早速UMLを書いてみましょう!
かっこいいですね!
なんだかSpring Securityをマスターした気分になります。
Filterはインタフェースなので、セキュリティ機能を実現するには具体的な実装が必要です。
FilterChainProxy
Spring Securityの主役は、「springSecurityFilterChain」という名前でBean定義されたサーブレットフィルターです。
そしてそのインスタンスはFilterChainProxyクラスのオブジェクトです。
Proxy(代理)という名前が付いているとおり、このクラス自身は具体的なセキュリティ機能を提供していません。
SecurityFilterChain
では、どのようにセキュリティ機能を提供しているのか?
SecurityFilterChainインタフェースに処理を委譲することで実現しています。
FilterChainProxyクラスは複数のSecurityFilterChainインタフェースを保持してます。
SecurityFilterChainはmatches()メソッドとgetFilters()メソッドを定義したインタフェースです。
FilterChainProxyがクライアントからリクエストを受けると、順番にSecurityFilterChainのmatches()メソッドを呼び出します。
matches()メソッドからtrueが返却されたSecurityFilterChainに対して、getFilters()メソッドを呼び出し、返却されたFilter群に処理を委譲することでセキュリティ機能を実現しています。
下に示すのが、そのコードの一部です。
private void doFilterInternal( ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { : List<Filter> filters = getFilters(fwRequest); : VirtualFilterChain vfc = new VirtualFilterChain( fwRequest, chain, filters); vfc.doFilter(fwRequest, fwResponse); } private List<Filter> getFilters(HttpServletRequest request) { for (SecurityFilterChain chain : filterChains) { if (chain.matches(request)) { return chain.getFilters(); } } return null; }
この内容からわかるとおり、複数のSecurityFilterChainを用いることで、リクエスト毎に異なるセキュリティ機能を適用することが可能となります。
例えば「public/**」に一致するURLに送信されたリクエストにはSecurity機能は適用しないけど、「private/**」に一致するURLに送信されたリクエストにはセキュリティ機能を適用するなどです。
なるほど、かっこいいですね!
コードからわかるように、最初にmatchしたSecurityFilterChainに処理を委譲する為、順番が重要になります。ここら辺の話はWebSecurityConfigurerAdapterのところで出てきます。
なお、上のクラス図の「AnySecurityFilter」は実際の存在するクラスではなく、認証・認可など、各種セキュリティ機能を提供するFilter実装クラスの総称です。
HttpFirewall
少し話はそれますが、FilterChainProxyクラスはHttpFirewallを保持しています。このインタフェースの機能を用いることで、ディレクトリトラバーサル攻撃やHTTPレスポンス分割攻撃に対するセキュリティを実現しています。このインタフェースのデフォルト実装はDefaultHttpFirewallクラスです。これらの機能はサーブレットリクエストやサーブレットレスポンスのラッパーを用いることで実現しているようです*2。
DelegatingFilterProxy
FilterChainProxyはspringSecurityFilterChainというフィルタ名で定義されたDelegatingFilterProxyクラスから呼ばれます。
ちょっとここら辺は混乱しました。通常、Bean定義されたサーブレットフィルタは自動的にサーブレットコンテナに登録されるみたいですが、どうも同じフィルタ名で定義されたフィルタがすでに存在している場合、自動登録の対象にはならないようです。
DelegatingFilterProxyクラスは自身のフィルタ名でBean定義されたサーブレットフィルタをDIコンテナから取り出して、そのサーブレットフィルタに処理を委譲するみたいです。
ややこしいですね。
おそらく、Servlet 3.0以降からサーブレットコンテナの初期化処理をJavaのコードで行うことが出来るようになったので、それ以前はDelegatingFilterProxyのようなBean定義されたサーブレットフィルタを取り出して実行するような仕組みが必要だったのだと思います。
Filterまわりの全体図
今までの話の全体図が次のとおりです。
Configのお話
WebSecurityとHttpSecurity
FilterChainProxyはWebSecurityクラスでインスタンス生成されます。
SecurityFilterChainの実装はDefaultSecurityFilterChainクラスで、WebSecurityクラスやHttpSecurityクラスから作成されます。WebSecurityクラスもHttpSecurityクラスもクラス名からは分かりづらいですが、ビルダークラスです。
WebSecurityクラスやHttpSecurityクラスには、サーブレットを生成する上で便利なメソッドがたくさん用意されています。このクラスの操作を理解することで、自由自在にシステム要件にあったセキュリティ機能が構築できるのですよ!
そして、WebSecurityクラスやHttpSecurityクラスのメソッドを実際に呼び出して設定するのがConfigureクラスになります。
WebSecurityConfigurerAdapter
WebSecurityConfigurerAdapterクラスは1つのHttpSecurityクラスのインスタンスを生成します。このクラスは継承して使われることを想定されていますが、このクラスの継承クラスの数だけ、SecurityFilterChainが出来上がると思っていいでしょう。なお、SecurityFilterChainは順番が重要だという話をしました。WebSecurityConfigurerAdapterはOrderアノーテーションなどを用いることで、順番を定義します。その順番に従い、SecurityFilterChainのmatches()メソッドが呼ばれる順番が決まります。なお、デフォルトの順序は100になっています。
Configurationのお話
最後にセキュリティ環境の話。
WebSecurityConfigurationクラスがspringSecurityFilterChainという名前でBean定義しており、FilterChainProxyのインスタンスをwebSecurityのbuild()メソッドで生成しています。また、このクラスがWebSecurityConfigurerAdapterの継承クラスと連携して、webSecurityの設定処理を呼び出します。
下に示すのが、そのコードの一部です。AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAMEは、"springSecurityFilterChain"という文字列のコンスタント値です。
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) public Filter springSecurityFilterChain() throws Exception { : return webSecurity.build(); }
このクラスがConfiguration定義されることで、セキュリティ機能が有効になるんですね!そして、EnableWebSecurityアノーテーションがこのConfiguration定義をimportしています。つまり、EnableWebSecurityアノーテーションを定義すると、セキュリティ機能全般が有効になるということなのです!
まるで魔法のようだ!
さらにさらに、SpringBootWebSecurityConfigurationがこのEnableWebSecurityアノーテーションを自動定義する機能を備えています。SpringBootで自動的にセキュリティ機能が有効にできるのは、SpringBootWebSecurityConfigurationのおかげなのです!