--- title: Spring Security 使い方メモ 基礎・仕組み tags: Java springframework SpringSecurity author: opengl-8080 slide: false --- [認証・認可の話](http://qiita.com/opengl-8080/items/032ed0fa27a239bdc1cc) [Remember-Me の話](http://qiita.com/opengl-8080/items/7c34053c74448d39e8f5) [CSRF の話](http://qiita.com/opengl-8080/items/3720ed415c2df26251a7) [セッション管理の話](http://qiita.com/opengl-8080/items/ad9159910501d1989876) [レスポンスヘッダーの話](http://qiita.com/opengl-8080/items/4e800eb6990653b0395c) [メソッドセキュリティの話](http://qiita.com/opengl-8080/items/76715fd3a92b6ca680d0) [CORS の話](http://qiita.com/opengl-8080/items/db0aa1ec0be36886953b) [Run-As の話](http://qiita.com/opengl-8080/items/49aa6381c16c8b9a7f3c) [ACL の話](http://qiita.com/opengl-8080/items/24a3118ef36bcf55ba71) [テストの話](http://qiita.com/opengl-8080/items/eaa8f4eb9286a3df7986) [MVC, Boot との連携の話](http://qiita.com/opengl-8080/items/4824d4a8c012f7ec53db) 番外編 [Spring Security にできること・できないこと](http://qiita.com/opengl-8080/items/6dc37f8b77abb5ae1642) # 環境 ## Java 1.8.0_xxx ## AP サーバー Tomcat 8.0.35 ## Gradle 3.2.1 ## OS Windows 10 # Spring Security とは [Spring](https://spring.io/projects) のプロジェクトの1つ。 主に Web アプリケーションにセキュリティの機能を追加するフレームワーク。 CSRF やセッション固定化攻撃など、良く知られた様々な攻撃に対する保護機能を提供してくれる。 # Hello World [こちら](http://qiita.com/opengl-8080/items/d4971ec4d2365c85ff99)は発表向けにもうちょっと順序立てて書いてます。 スライドと合わせて読むと、より分かりやすいかもしれない(スライドの方にしか書いてないこととかもあった気がする)。 ## 実装 ```text:フォルダ構成 |-build.gradle `-src/main/webapp/ |-index.jsp `-WEB-INF/ |-applicationContext.xml `-web.xml ``` ```groovy:build.gradle apply plugin: 'war' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' repositories { mavenCentral() } dependencies { providedCompile 'javax.servlet:javax.servlet-api:3.1.0' compile 'javax.servlet:jstl:1.2' compile 'org.springframework.security:spring-security-web:4.2.1.RELEASE' compile 'org.springframework.security:spring-security-config:4.2.1.RELEASE' } war.baseName = 'spring-security-sample' ``` ```web.xml springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy springSecurityFilterChain /* org.springframework.web.context.ContextLoaderListener ``` ```xml:applicationContext.xml ``` ```jsp:index.jsp <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@page contentType="text/html" pageEncoding="UTF-8"%> Hello Spring Security!!

Hello Spring Security!!

``` ## 動作確認 `gradle war` で war ファイル(`spring-security-sample.war`)を生成し、 Tomcat にデプロイする。 デプロイが完了したら、ブラウザで `http://localhost:8080/spring-security-sample` を開く。 ![spring-security.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/0eba6ae8-55ec-505b-9757-c1e4bcd74501.jpeg) 謎のログイン画面が表示される。 User, Password にそれぞれ `hoge`, `HOGE` と入力して `Login` ボタンをクリックする。 ![spring-security.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/abedee5c-d13f-503b-ec97-c6e850362d38.jpeg) `index.jsp` の内容が表示される。 最後に `logout` ボタンをクリックする。 ![spring-security.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/70bc12de-2352-d35a-f097-74576598221c.jpeg) ログアウトが完了して、ログイン画面に戻る。 ## 説明 ### 依存関係 ```groovy:build.gradle compile 'org.springframework.security:spring-security-web:4.2.1.RELEASE' compile 'org.springframework.security:spring-security-config:4.2.1.RELEASE' ``` 最小構成では `spring-security-web` と `spring-security-config` を依存に追加すれば[いいらしい](http://docs.spring.io/spring-security/site/docs/4.2.1.RELEASE/reference/htmlsingle/#gradle)。 **spring-security-web** `Filter` とか Web アプリに関係するコードが含まれている。Web の認証機能や URL ベースのアクセス制御が必要な場合は、このモジュールが必要になる。 メインパッケージは `org.springframework.security.web` **spring-security-config** xml で定義を記述する際に使用する namespace を解析するコードや Java コンフィグのためのコードが含まれている。XML ベースの設定や Java コンフィグを利用する場合は、このモジュールが必要になる。 メインパッケージは `org.springframework.security.config` ここに含まれるクラスを、アプリケーションが直接利用することはない。 ### Spring コンテナの初期化 ```xml:web.xml org.springframework.web.context.ContextLoaderListener ``` `ContextLoaderListener` をリスナーとして登録することで、 Spring のコンテナ(`ApplicationContext`)が初期化される。 `ApplicationContext` クラスに何を使用するか明示しない場合、デフォルトで `XmlWebApplicationContext` が使用される。 `spring-web-x.x.x.RELEASE.jar` の `org/springframework/web/context/ContextLoader.properties` に以下のように定義されていて、サーブレットコンテナが初期化されるときに `ContextLoaderListener` が読み込んでる。 ```properties:ContextLoader.properties # Default WebApplicationContext implementation class for ContextLoader. # Used as fallback when no explicit context implementation has been specified as context-param. # Not meant to be customized by application developers. org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext ``` `XmlWebApplicationContext` は、デフォルトで `WEB-INF/applicationContext.xml` を設定ファイルとして読み込む。 ### Spring Security の設定 ```xml:applicationContext.xml ``` `applicationContext.xml` 自体は Spring の Bean 定義をするための標準的な設定ファイルで、 Spring Security 用の特別な設定ファイルというわけではない。 `xmlns` で `http://www.springframework.org/schema/security` を読み込むことで、 Spring Security 用のタグが使用できるようになる(リファレンスでは、この Spring Security 専用のタグを使うことを **namespace** と呼んでいる)。 このタグを使用することで、 Spring Security の設定をこのファイル上で行うことができる。 上の設定は、ざっくりと次のようなことを定義している。 - `/login` へのアクセスは誰でもOK(`permitAll`) - それ以外のすべてのパスへのアクセス(`/**`)は、認証されている必要がある(`isAuthenticated()`) - ログイン方法は Form ログイン - ログアウトができるようにする - ユーザー情報として名前 = `hoge`、パスワード = `HOGE` で、 `ROLE_USER` というロールを持つユーザーを定義 以下、個々のタグについて少し細かく説明する。 --- **``** `` タグを定義すると、自動的にコンテナにいくつかの Bean が登録される。 その中でも重要な Bean として、次の2つのクラスがある。 1. `FilterChainProxy` 2. `SecurityFilterChain` `FilterChainProxy` は、 **`"springSecurityFilterChain"`** という Bean 名でコンテナに登録される。 このクラスは Spring Security の処理の入り口となる。 `SecurityFilterChain` は、それ自体はただのインターフェースで、実装クラスとしては `DefaultSecurityFilterChain` がコンテナに登録される。 `SecurityFilterChain` は、名前の通りセキュリティ機能を持つ `javax.servlet.Filter` をチェイン(連鎖)させるもので、複数の `Filter` を内部に保持する。 Spring Security は、 `Filter` を利用することでセキュリティの機能を実現している。 `FilterChainProxy` は `Proxy` と名前に付いていることからも分かる通り、このクラス自体は処理を行わない。 具体的な処理は `SecurityFilterChain` が持つ `Filter` 達に委譲する。 --- **``** URL のパターンごとに、アクセスに必要となる条件(アクセス制御)を定義している。 `pattern` 属性は、[ant パスの形式](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/AntPathMatcher.html)で記述することができる。 `access` 属性には、 `pattern` 属性で指定した URL にアクセスするために必要となる条件を指定する。 `permitAll` は、全てのアクセスを許可する(認証不要)ことを意味している。 そして、 `isAuthenticated()` は、認証済み(ログイン済み)であればアクセスを許可することを意味している。 `access` 属性は、 Spring Expression Language (SpEL) と呼ばれる Spring 独自の式言語を用いて記述する。 --- **``** Form 認証が必要なことを定義している。 認証エラーの場合は、デフォルトで `/login` にリダイレクトさせられる。 デフォルトの場合、`/login` にアクセスすると Spring Security が用意している簡易なログインページが表示される。 このデフォルトのログインページは、`spring-security-web-x.x.x.RELEASE.jar` の中の `DefaultLoginPageGeneratingFilter` によって生成されている。 --- **``** このタグを追加することで、ログアウトができるようになる。 デフォルトでは、 `/logout` に対して POST リクエストを実行するとログアウトが行われるようになっている。 --- **``** `AuthenticationManager` を Bean として定義する。 `AuthenticationManager` は認証処理を実行するクラスで、必ず定義しなければならない。 `AuthenticationManager` 自体はインターフェースで、実装クラスには `ProviderManager` が使用される。 `ProviderManager` 自体は具体的な認証処理は行わず、後述する `AuthenticationProvider` に認証処理を委譲する。 --- **``** `AutheticationProvider` を Bean として定義する。 このインターフェースは認証の種類に応じた具体的な認証処理を提供する。 例えば、 LDAP の認証処理を提供するクラスは `LdapAuthenticationProvider` という感じ。 このタグを宣言した場合は、 `DaoAuthenticationProvider` がコンテナに登録される。 このクラスは、 `UserDetailsService` からユーザーの情報を取得して認証処理を行う。 --- **``** `UserDetailsService` を Bean として登録する。 このインターフェースはユーザーの詳細な情報(`UserDetails`)を取得してくる機能を提供する。 このタグを宣言すると、 `InMemoryUserDetailsManager` がコンテナに登録される。 このクラスは、名前の通りメモリ上にユーザー情報を保持しておくようになっている。いわば動作確認用の仮実装になる。 --- **``** `UserDetails` のインスタンスを定義する。 このインターフェースには、ユーザーの詳細な情報にアクセスするための Getter メソッドなどが定義されている。 このタグを宣言すると、 `User` というクラスのインスタンスが生成される。 `name`, `password`, `authorities` 属性でユーザーを識別するための名前、パスワード、権限を指定することができる。 ### Spring Security をアプリケーションに適用する ```xml:web.xml springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy springSecurityFilterChain /* ``` `DelegatingFilterProxy` をサーブレットフィルタとして `web.xml` に定義する。 このとき、 `` を `"springSecurityFilterChain"` という名前で定義する。 これで、 `` で定義した URL にアクセスがあると、 Spring Security が適用されるようになる。 `DelegatingFilterProxy` は、自身の `` に設定された名前を使って Spring コンテナから `javax.servlet.Filter` を実装した Bean を取得する。 そして、その Bean に処理を委譲するだけのサーブレットフィルタになっている。 ここでは `` に `"springSecurityFilterChain"` を指定している。 この名前は、 `applicationContext.xml` で `` タグを使用したときにコンテナに自動的に登録される `FilterChainProxy` の Bean 名と一致している。 つまり、 `DelegatingFilterProxy` は、サーブレットフィルタと Spring Security(`FilterChainProxy`)の橋渡しをするのが役割になっている。 ### CSRF 対策 ```jsp:index.jsp <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@page contentType="text/html" pageEncoding="UTF-8"%> Hello Spring Security!!

Hello Spring Security!!

``` デフォルトだと、 CSRF 対策が有効になる。 そのため、リクエストを送信するときは CSRF 対策用のトークンを一緒に渡す必要がある。 トークンの値やパラメータ名は、リクエストスコープに `_csrf` という名前で保存されている。 CSRF 対策についてのより詳細な説明は後日。。。 ### まとめ サーバーを起動してからログインするまでの間に、裏でどのように処理が流れているかまとめる。 シーケンス図を利用しているが、分かりやすさ優先のため UML の厳密な記法には従っていないので悪しからず。 #### サーバー起動時の流れ 一部厳密ではないが、だいたい以下のような雰囲気で初期化→認証処理が行われていると思う。 ![SpringSecurity初期化時の動き.png](https://qiita-image-store.s3.amazonaws.com/0/28302/87384b9e-afc0-65ed-c6a5-2eefaddd39e3.png) 1. サーブレットコンテナが起動し、 `web.xml` に `Listener` として登録されている `ContextLoaderListener` が実行される。 2. `XmlWebApplicationContext` のインスタンスが生成され、 `/WEB-INF/applicationContext.xml` が読み込まれる。 3. `` タグによって `FilterChainProxy` のインスタンスが `"springSecurityFilterChain"` という名前で Spring コンテナに登録される。 4. `web.xml` に `Filter` として登録されている `DelegateFilterProxy` が、サーブレットコンテナによって生成される。 5. リクエストがあると、 `DelegateFilterProxy` が呼ばれ、自身の名前(`"springSecurityFilterChain"`)で Spring コンテナから Bean を取得する(`FilterChainProxy` が取得される)。 6. `FilterChainProxy` に処理が委譲される。 #### 未認証時にアクセスしてログイン画面に飛ばされるときの流れ 同じく厳密ではないが、ログインしていない状態でアクセスしたときに、ログイン画面にリダイレクトされるときの流れは次のような感じだと思う。 ![ログイン画面に飛ばされるときの流れ.png](https://qiita-image-store.s3.amazonaws.com/0/28302/d186722e-8225-e3b2-ce6e-b78f2a45410c.png) 1. `FilterSecurityInterceptor` が `AuthenticationManager` の認証処理を呼ぶ。 2. `AuthenticationManager` が認証処理を行うが、未認証なので `AuthenticationException` をスローする。 3. スローされた例外を、 `ExceptionTranslationFilter` がキャッチする。 4. `LoginUrlAuthenticationEntryPoint` によって、ログイン画面にリダイレクトさせられる。 #### ログイン画面にアクセスしてから、実際にログインを行うときの流れ `LoginUrlAuthenticationEntryPoint` によって `/login` にリダイレクトさせられた直後から、ユーザー名とパスワードを入力してログインを行うところまでの流れ。 ![ログイン実行時の流れ.png](https://qiita-image-store.s3.amazonaws.com/0/28302/de6960bb-f403-63bc-a76d-b6da8180ea8c.png) 1. `DefaultLoginPageGeneratingFilter` が、デフォルトのログイン画面を生成する。 2. ユーザーがログインを実行する。 3. `/login` に POST リクエストがあると `UsernamePasswordAuthenticationFilter` が `AuthenticationManager` に認証処理を委譲する。 4. `AuthenticationManager` の実装クラスである `ProviderManager` は、 `AuthenticationProvider` に認証処理を委譲する。 5. `AuthenticationProvider` の実装クラスである `DaoAuthenticationProvider` は、 `UserDetailsService` からユーザー情報(`UserDetails`)を取得して、認証処理を実行する。 6. 認証に成功すると、 `UsernamePasswordAuthenticationFilter` は `AuthenticationSuccessFilter` に認証成功時の処理を委譲する。 7. `AuthenticationSuccessFilter` の実装クラスである `SavedRequestAwareAuthenticationSuccessHandler` は、ログイン画面に飛ばされる前にアクセスしようとしていた URL を復元し、リダイレクトを行う。 - ログイン前にアクセスしていた URL は、 `HttpSession` に保存されている。 # namespace を使わないとどうなるか ```xml:applicationContext.xml ``` namespace (専用タグ)を使うと、 Spring Security を動かすのに必要となる様々な Bean が自動的にコンテナに登録される。 このおかげで、 Spring Security の設定は非常にシンプルに記述できるようになっている。 しかし、認証機能を拡張する必要が出てくると、裏でどのような Bean が登録され、それらがどのような関係を持っているかを理解することは重要になってくる。 それを理解するため、 Hello World で作成した設定ファイルを、 namespace を使わずに書き直してみる。 ただし、 namespace での設定と完全に一致させるところまでは頑張らず、 Hello World で見た一連のログイン処理を再現させるところまでにする。 なので、一部の Bean 定義などは省略している。 ```xml:applicationContext.xml ``` クラス図にすると、次のような感じ。 ![HelloWorldの仕組み.png](https://qiita-image-store.s3.amazonaws.com/0/28302/2652deac-36ec-05ff-7f99-3cfaa998c715.png) もうちょっとざっくりと表現すると、 ![spring-security.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/d91bcc01-4159-2d12-9811-c2dc20188001.jpeg) こんな感じ。 `FilterChainProxy` が入り口となり、 `SecurityFilterChain` によってまとめられた各種 `Filter` 達が順番に実行され、 `FilterSecurityInterceptor` が保護対象(secure object)の直前で認証・認可の処理を行っている。 ※メソッドセキュリティの話は今は外している。 # セキュアオブジェクト Spring Security のリファレンスを読んでいると、ちょくちょく **secure object** (セキュアオブジェクト)という言葉が出てくる。 これは Spring Security が定義した用語で、セキュリティが確保された(確保すべき)対象を表している。 ※「オブジェクト」はオブジェクト指向のインスタンスを指しているのではなく、純粋に「対象」という意味で使われているものと思われる。 代表的なものとして、「Web のリクエスト」や「メソッドの実行」などがある。 # FilterSecurityInterceptor は Filter なのか Interceptor なのか `FilterSecurityInterceptor` について、最初その名前を見たとき「これは `Filter` なのか `Interceptor` なのか、どっちなんだ?」と混乱した。 このクラスは `Filter` インターフェースを実装しているので、一見すると `Filter` っぽい。 答えというか、自分の中でこのクラスの正体についてしっくり来たのが、このクラスの型階層を見たとき。 ![FilterSecurityIntercpetorのクラス階層.png](https://qiita-image-store.s3.amazonaws.com/0/28302/6f70823d-1c04-9b6d-a6d5-8bce23c2bf25.png) `AbstractSecurityInterceptor` を同じ親に持つ、 `MethodSecurityInterceptor` が兄弟クラスとして存在している。 つまり、 `Filter` のセキュリティのための `Interceptor` が `FilterSecurityInterceptor` で、 メソッド実行のセキュリティのための `Interceptor` が `MethodSecurityInterceptor` ということで、これらはあくまで `Interceptor` ということになる。 `FilterSecurityInterceptor` は、言うなれば「`Filter` の `Filter` による `Filter` のための `Interceptor`」ということなのだろう。たぶん。 # Java Configuration Spring Security 3.2 からは、 Spring 3.1 で追加された Java Configuration が利用できるようになっている。 Java Configuration を利用すると、 XML ファイルなしで namespace と同様の設定を記述できる。 Java Configuration を使うことで、ある程度の設定ミスはコンパイラがチェックしてくれる・リファクタリングが容易になる、といったメリットがある。 ## Hello World を Java Configuration で置き換える ### 実装 ```text:フォルダ構成 |-build.gradle `-src/main/ |-java/ | `-sample/spring/security/ | `-MySpringSecurityConfig.java `-webapp/ |-index.jsp `-WEB-INF/ `-web.xml ``` ```xml:web.xml springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy springSecurityFilterChain /* org.springframework.web.context.ContextLoaderListener contextClass org.springframework.web.context.support.AnnotationConfigWebApplicationContext contextConfigLocation sample.spring.security.MySpringSecurityConfig ``` ```java:MySpringSecurityConfig.java package sample.spring.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .formLogin(); } @Autowired public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("hoge").password("HOGE").roles("USER"); } } ``` `index.jsp` は変更なし。 ### 説明 #### コンテナの実装を変更する ```xml:web.xml springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy springSecurityFilterChain /* org.springframework.web.context.ContextLoaderListener contextClass org.springframework.web.context.support.AnnotationConfigWebApplicationContext contextConfigLocation sample.spring.security.MySpringSecurityConfig ``` デフォルトで使用されていた `XmlWebApplicationContext` ではなく、 `AnnotationConfigWebApplicationContext` を使うように設定を変更する。 `contextClass` という名前で `` を定義すると、 `ContextLoaderListener` はそこで指定したクラスをコンテナとして利用するようになる。 さらに `AnnotationConfigWebApplicationContext` が読み込む設定クラスは、 `contextConfigLocation` という名前で `` を定義することで指定できる。 #### Spring Security を有効にする ```java:MySpringSecurityConfig.java package sample.spring.security; ... import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter { ... } ``` Spring Security の Java Configuration を有効にするには、 `AnnotationConfigWebApplicationContext` で読み込むクラスを `@EnableWebSecurity` でアノテートする。 なぜこれで Spring Security が有効になるのかは、このアノテーションの実装を見るとわかる。 ```java:EnableWebSecurity.java ... package org.springframework.security.config.annotation.web.configuration; ... import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication; ... @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class }) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity { ... } ``` `@Import` を使って `WebSecurityConfiguration` が読み込まれている。 このクラスも `@Configuration` でアノテートされた設定クラスになっている。 ```java:WebSecurityConfiguration.java ... package org.springframework.security.config.annotation.web.configuration; ... import javax.servlet.Filter; ... @Configuration public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { ... @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) public Filter springSecurityFilterChain() throws Exception { ... return webSecurity.build(); } ... } ``` `AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME` の値は `"springSecurityFilterChain"` になっている。 `webSecurity.build()` の戻り値は `FilterChainProxy` になっており、ちょうど xml でやっていたのと同じ形で Bean が登録されるようになっている。 なので、 `@EnableWebSecurity` でアノテートすることで Spring Security が有効になる。 #### Spring Security の設定 ```java:MySpringSecurityConfig.java package sample.spring.security; ... import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .formLogin(); } ... } ``` Spring Security の具体的な設定をしていくには、まず `WebSecurityConfigurerAdapter` というクラスを継承する。 `WebSecurityConfigurerAdapter` は、デフォルトでシンプルな `SecurityFilterChain` を構築するようになっている。 このクラスを継承して `configure(HttpSecurity)` などのメソッドをオーバーライドすることで、構築される `SecurityFilterChain` を任意にカスタマイズすることができる。 引数で受け取る `HttpSecurity` が、 namespace でいう `` タグに対応している。 基本的に `` でできた設定は `HttpSecurity` でもできるようになっている。 上記の実装は、下記の XML と同じ意味になる。 (※ログアウトは `WebSecurityConfigurerAdapter` が内部で自動的に登録しているので省略している) ```xml:applicaitonContext.xml ``` `authorizeRequests()` メソッドは namespace でいうところの `` の設定を開始するメソッドになる。 メソッドの戻り値の型が `ExpressionInterceptUrlRegistry` というクラスに代わっており、専用の設定メソッドが呼べるようになっている。 `` の設定を終了させて別の設定を続けたい場合は、 `and()` メソッドを挟む。 `and()` メソッドは `HttpSecurity` を返すので、メソッドチェーンを途切れさせることなく次の設定を続けることができる。 `formLogin()` は、名前の通り Form 認証を有効にしている。 #### ユーザー情報の定義 ```java:MySpringSecurityConfig.java package sample.spring.security; ... import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; @EnableWebSecurity public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter { ... @Autowired public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("hoge").password("HOGE").roles("USER"); } } ``` `AuthenticationManagerBuilder` は `AuthenticationManager` 以下のクラスを定義するためのサポートクラスで、上記実装のようにメソッドチェーンを使って `UserDetailsService` などを定義することができる。 ちなみに、 `AuthenticationManagerBuilder` を使わずに直接 Bean を定義するメソッドを宣言しても良い。 ```java:Bean定義で設定する場合 package sample.spring.security; import org.springframework.context.annotation.Bean; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; ... @EnableWebSecurity public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter { ... @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("hoge").password("HOGE").roles("USER").build()); return manager; } } ``` ### Java Configuration 初期化の流れ ざっくりと、次のような流れで初期化されているもよう。 ※図を見やすくするため、意図的にメソッドの呼び出しや表記を省略しています(`doBuild()` とか)。  厳密な流れを知りたい場合は、下図を参考に実際の実装を追ってみてください。 ![JavaConfigの初期化.png](https://qiita-image-store.s3.amazonaws.com/0/28302/a1f9b975-5b8e-16df-0e4e-7acb046d1a50.png) # web.xml を使わない AP サーバが Servlet 3.0 以上なら、 web.xml 無しでも Spring Security を使い始められる。 ## 実装 ```text:フォルダ構成 |-build.gradle `-src/main/ |-java/ | `-sample/spring/security/ | |-MySpringSecurityInitializer.java | `-MySpringSecurityConfig.java `-web/ `-index.jsp ``` ```java:MySpringSecurityInitializer.java package sample.spring.security; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; public class MySpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer { public MySpringSecurityInitializer() { super(MySpringSecurityConfig.class); } } ``` `MySpringSecurityConfig.java` と `index.jsp` はこれまでの奴と一緒なので割愛。 ## 説明 `web.xml` を削除して、代わりに `MySpringSecurityInitializer` というクラスを追加している。 このクラスは、 `AbstractSecurityWebApplicationInitializer` を継承して作成する。 そして、コンストラクタで Java Configuration のクラス(`MySpringSecurityConfig`)の `Class` オブジェクトを親クラスに渡すように実装する。 こうすることで、 `web.xml` でしていた設定が全部不要になる。 ### 仕組み この機能の実現には、 Servlet 3.0 で追加されたいくつかの仕組みが利用されている。 どのようにして `web.xml` なしで設定が実現されているのか、その仕組みを見ていく。 #### ServletContainerInitializer `AbstractSecurityWebApplicationInitializer` は `WebApplicationInitializer` というインターフェースを実装している。 この `WebApplicationInitializer` の Javadoc には次のように記載されている。 ```java:WebApplicationInitializer.java /** * ... * *

Implementations of this SPI will be detected automatically by {@link * SpringServletContainerInitializer}, which itself is bootstrapped automatically * by any Servlet 3.0 container. See {@linkplain SpringServletContainerInitializer its * Javadoc} for details on this bootstrapping mechanism. * ... */ public interface WebApplicationInitializer { ``` このインターフェースを実装したクラスは、 Servlet 3.0 以上の環境だと `SpringServletContainerInitializer` というクラスによって自動的に読み込まれるようになっているらしい。 `SpringServletContainerInitializer` の実装を見に行く。 ```java:SpringServletContainerInitializer.java package org.springframework.web; ... import javax.servlet.ServletContainerInitializer; import javax.servlet.annotation.HandlesTypes; ... @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { ... ``` `ServletContainerInitializer` を実装し、 `@HandlesTypes` でアノテートされている。 どちらも Servlet 3.0 で追加されている。 Servlet コンテナが起動するときに、このクラスの `onStartup(Set>, ServletContext)` メソッドが実行される。 メソッドの第一引数には、 `@HandlesTypes` で指定された `Class` に関係する[^1]クラスがコンテナによって自動的に検索され、渡される。 `SpringServletContainerInitializer` の場合は `WebApplicationInitializer` が指定されているので、このインターフェースを実装したクラスが Servlet コンテナによって検索されて渡ってくる。 [^1]: インターフェースなら実装クラス、抽象・具象クラスならそのクラスおよびサブクラス、アノテーションならアノテートされたクラスが対象になる `SpringServletContainerInitializer` の `onStartup()` メソッドが実行されると、 `WebApplicationInitializer` を実装したクラスのインスタンスを生成して、 `WebApplicationInitializer` の `onStartup(ServletContext)` メソッドを実行する。 #### AbstractSecurityWebApplicationInitializer `AbstractSecurityWebApplicationInitializer` の `onStartup(ServletContext)` メソッドは次のような実装になっている。 ```java:AbstractSecurityWebApplicationInitializer.java package org.springframework.security.web.context; import java.util.Arrays; import java.util.EnumSet; import java.util.Set; import javax.servlet.Filter; import javax.servlet.ServletContext; ... import org.springframework.context.ApplicationContext; ... import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.AbstractContextLoaderInitializer; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.filter.DelegatingFilterProxy; ... public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer { ... public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain"; ... public final void onStartup(ServletContext servletContext) throws ServletException { beforeSpringSecurityFilterChain(servletContext); if (this.configurationClasses != null) { AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); ★ rootAppContext.register(this.configurationClasses); servletContext.addListener(new ContextLoaderListener(rootAppContext)); ★ } if (enableHttpSessionEventPublisher()) { servletContext.addListener( "org.springframework.security.web.session.HttpSessionEventPublisher"); } servletContext.setSessionTrackingModes(getSessionTrackingModes()); insertSpringSecurityFilterChain(servletContext); afterSpringSecurityFilterChain(servletContext); } ... private void insertSpringSecurityFilterChain(ServletContext servletContext) { String filterName = DEFAULT_FILTER_NAME; DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy( filterName); ★ String contextAttribute = getWebApplicationContextAttribute(); if (contextAttribute != null) { springSecurityFilterChain.setContextAttribute(contextAttribute); } registerFilter(servletContext, true, filterName, springSecurityFilterChain); } ... } ``` 何やら見覚えのあるクラスがちらほらと... 要は、 `web.xml` でやっていた設定がこの実装の中で行われている。 #### まとめ だいたい次のような流れて初期化されている。 (例によって、簡略化のため厳密ではない部分はあります) ![Servlet3.0での初期化の流れ.png](https://qiita-image-store.s3.amazonaws.com/0/28302/492fe6fd-7c35-c98e-10b0-48844353e6e3.png) # 参考 - [Spring Security Reference](http://docs.spring.io/spring-security/site/docs/4.2.1.RELEASE/reference/htmlsingle/) - [TERASOLUNA Server Framework for Java (5.x) Development Guideline — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.3.0.RELEASE documentation](http://terasolunaorg.github.io/guideline/5.3.0.RELEASE/ja/) - [Tomcat 7の新機能で何ができるようになるのか?(1):Tomcat 7も対応したServlet 3.0の6つの主な変更点 (3/3) - @IT](http://www.atmarkit.co.jp/ait/articles/1104/12/news134_3.html) - [HandlesTypes annotation examples](http://www.codejava.net/java-ee/servlet/handlestypes-annotation-examples) - [annotations - Benefits of JavaConfig over XML configurations in Spring? - Stack Overflow](http://stackoverflow.com/questions/29162278/benefits-of-javaconfig-over-xml-configurations-in-spring)