Hello World で学ぶ Spring Security の仕組み

## 自己紹介
<img src="https://qiita-image-store.s3.amazonaws.com/0/28302/63d28421-104b-3066-2712-768e6aded5f7.jpeg" width="200px" class="pull-right"/>

- opengl-8080
- 主に Qiita で技術メモを書いたり
- 関西の SIer 勤務

---
## 今日お話しすること
簡単な Hello World を通じて、 Spring Security の仕組みの基礎的な部分を説明

- どのようなクラスが、どのように連携しあっているのか
- 設定ファイルがどのように関係しているのか

---
## 背景
- 個人的に Spring Security の勉強を開始
- ちょっと Hello World を書こうとしたが手こずる
    - この設定はなんで必要？
    - ・・・と書くとなぜ～～～が有効に？
    - この設定って最小限の Hello World で必要？

---
## 抽象化された設定
- Spring Security の設定は高度に抽象化されている
- 設定が簡潔になる一方で、裏で何が行われているかが分かりづらい
- 仕組みの理解や、カスタマイズがしづらくなる

※個人の所感です

---
## 対象者
Hello World を通じて Spring Security の仕組みを学ぶことで、

- Spring Security をこれから勉強する人の参考に<font class="small" color="gray">（なれるように）</font>
- 仕組みを知らずに Spring Security を使ってた人の参考に<font class="small" color="gray">（なれるように）</font>

### 前提知識
- Servlet, JSP について、そこそこの知識を持っている
    - `Filter` などを知っており、 `web.xml` を書いて Servlet アプリを構築できる
- Spring についてそこそこの知識を持っている
    - DI コンテナが何なのか、 Bean や AOP を理解している


---
## Hello World アプリ（前提）
### Spring Boot は使わない
- Spring Security の基礎を学ぶのが目的
- 素の Spring Security だけを使う

### Java Config は使わない
- xml で設定を記述する
- 結局 Java Config もやってることは同じ

### Servlet 3.0 の機能は使わない
- Servlet 3.0 の機能を利用すれば web.xml なしで設定が可能
- 仕組みの基礎を学ぶのが目的なので、こちらもやはり使わないようにする

---
## Hello World アプリ（環境構築）
### ソース
https://github.com/opengl-8080/spring-security-hello-world

### 動作確認環境
- Java 1.8.0_121
- Tomcat 8.0.35

### war ファイルの入手
- [こちら](https://github.com/opengl-8080/spring-security-hello-world/releases/tag/v1.0.0) から `spring-security.war` をダウンロード
- ソースからビルドする場合は、ソースをダウンロードしてプロジェクトのルートで `gradlew war` を実行。

### 動作確認
- Tomcat に `spring-security.war` をデプロイ
- `http://localhost:8080/spring-security` にアクセス

---
## Hello World アプリ（動作）

`http://localhost:8080/spring-security` にアクセス

![spring-security.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/8ee15cb2-ff81-d1ea-d6ef-f3eb42b4d82b.jpeg)

User, Password に `foo` と入力してログイン

![spring-security.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/bd914f41-ec04-2dd7-58bb-d12109137598.jpeg)

403 エラーになる

次は `bar` でログイン

![spring-security.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/47c59fe4-0677-af50-318c-26b92b3f69cb.jpeg)

![spring-security.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/84ebed4d-c079-bcf8-75b3-09d0dde7fb92.jpeg)

index ページが表示される。

logout ボタンをクリックすればログアウトができる。

---
## Hello World アプリ（実装内容）
```text:ファイル構成
|-build.gradle : Gradle のビルドファイル
|
`-src/main/webapp/
  |
  |-index.jsp : インデックスページ
  |
  `-WEB-INF/
    |
    |-applicationContext.xml : Spring 設定ファイル
    |
    `-web.xml : Servlet 設定ファイル
```

- 全４ファイル
    - １つはビルドファイルなので、実際デプロイされるのは３ファイル
- Java ソース一切なしの超シンプル構成

```groovy:build.gradle
apply plugin: 'war'

sourceCompatibility = '1.8'
targetCompatibility = '1.8'
compileJava.options.encoding = 'UTF-8'

repositories {
    mavenCentral()
}

dependencies {
    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'

task wrapper(type: Wrapper) {
    gradleVersion = '3.2.1'
}
```

```jsp:index.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>
      Hello Spring Security!!
    </title>
  </head>
  <body>
    <h1>
      Hello Spring Security!!
    </h1>
    
    <form action="logout" method="post">
      <input type="submit"
             value="logout" />
      <input type="hidden"
             name="${_csrf.parameterName}"
             value="${_csrf.token}" />
    </form>
  </body>
</html>
```

- ログイン後に表示されるトップページ
- メッセージとログアウトボタンがあるだけ
- `_csrf` についてはデフォルトで有効になっているため設定していますが、この後の説明では特に登場しないので詳細な説明は省きます
    - 詳細は [Spring Security 使い方メモ　CSRF | Qiita](https://qiita.com/opengl-8080/items/3720ed415c2df26251a7) を参照してください


```xml:web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  
  <filter>
    <filter-name>
      springSecurityFilterChain
    </filter-name>
    <filter-class>
      org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
  </filter>

  <filter-mapping>
    <filter-name>
      springSecurityFilterChain
    </filter-name>
    <url-pattern>
      /*
    </url-pattern>
  </filter-mapping>
</web-app>
```

```xml:applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/security
         http://www.springframework.org/schema/security/spring-security.xsd">

  <sec:http>
    <sec:intercept-url
            pattern="/login"
            access="permitAll" />
    <sec:intercept-url
            pattern="/**"
            access="isAuthenticated() and hasAuthority('BAR')" />
    <sec:form-login />
    <sec:logout />
  </sec:http>
  
  <sec:authentication-manager>
    <sec:authentication-provider>
      <sec:user-service>
        <sec:user name="foo"
                  password="foo"
                  authorities="" />
        <sec:user name="bar"
                  password="bar"
                  authorities="BAR" />
      </sec:user-service>
    </sec:authentication-provider>
  </sec:authentication-manager>
</beans>
```

---
## Hello World を読み解く
1. サーバー起動～リクエスト受付
2. Spring Security の入り口
3. ログイン処理
4. アクセス制御
5. ログアウト処理

---
## Hello World を読み解く
1. **サーバー起動～リクエスト受付**
2. Spring Security の入り口
3. ログイン処理
4. アクセス制御
5. ログアウト処理

---
## Spring コンテナの初期化
```xml:web.xml
<listener>
  <listener-class>
    org.springframework.web.context.ContextLoaderListener
  </listener-class>
</listener>
```

- `<listener>`
    - Servlet の機能で、アプリケーション起動時に実行する処理を定義できる
- `ContextLoaderListener`
    - Spring コンテナを初期化するクラス
    - デフォルトで `XmlWebApplicationContext` が使用される
    - 設定ファイルとして `WEB-INF/applicationContext.xml` が使用される

---
## DelegatingFilterProxy の登録
```xml:web.xml
  <filter>
    <filter-name>
      springSecurityFilterChain
    </filter-name>
    <filter-class>
      org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
  </filter>

  <filter-mapping>
    <filter-name>
      springSecurityFilterChain
    </filter-name>
    <url-pattern>
      /*
    </url-pattern>
  </filter-mapping>
```

- `DelegatingFilterProxy` という Filter を `springSecurityFilterChain` という名前で登録
    - Filter は Servlet の提供する機能
    - リクエストの前後に任意の処理を挟むことができる
- `url-pattern` に `/*` を指定して、全てのリクエストを処理

---
## DelegatingFilterProxy の役割
![DelegatingFilterProxyの役割.png](https://qiita-image-store.s3.amazonaws.com/0/28302/2df45469-99fe-5aff-b8e9-dbac9239d7fb.png)

- `DelegatingFilterProxy` は、 Spring コンテナから次の条件に一致する Bean を検索する
    - Bean の名前が自身の Filter 名（`springSecurityFilterChain`）と一致する
    - `javax.servlet.Filter` インターフェースを実装している
- 取得した Bean に処理を委譲する
- `DelegatingFilterProxy` が処理を委譲したクラスは Spring コンテナから取得した Bean なので、 Spring コンテナの機能を利用することができる
    - DI
    - AOP
    - etc...
- `DelegatingFilterProxy` の役割は、 Servlet コンテナと Spring コンテナの橋渡し

---
## Spring Security の設定
```xml:applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       ...>

  <sec:http>
    <sec:intercept-url
            pattern="/login"
            access="permitAll" />
    <sec:intercept-url
            pattern="/**"
            access="isAuthenticated() and hasAuthority('BAR')" />
    <sec:form-login />
    <sec:logout />
  </sec:http>

  ...
```

- `applicationContext.xml` は Spring の設定ファイル
    - `<bean>` タグを使って Bean を定義する
    - Spring Security 専用の設定ファイルというわけではない
- Spring Security の設定を簡潔に記述できるようにするため、専用のタグが用意されている
    - リファレンスでは **namespace** と呼んでいる
    - `xmlns:sec` で読み込んでいる

---
## `<http>` が肝
```xml:applicationContext.xml
  <sec:http>
    ...
  </sec:http>
```

- この `<http>` が Spring Security の設定の肝
- 重要な Bean が大量に登録される

---
## FilterChainProxy
- `<http>` タグが登録する Bean の１つ
- `Filter` インターフェースを継承している
- `springSecurityFilterChain` という名前で Spring コンテナに登録される
- `DelegatingFilterProxy` が委譲する Bean の正体

---
## まとめ <span class="h2">(サーバー起動～リクエスト受付)</span>
![test](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. `<http>` タグによって `FilterChainProxy` のインスタンスが `"springSecurityFilterChain"` という名前で Spring コンテナに登録される。
4. `web.xml` に `Filter` として登録されている `DelegateFilterProxy` が、サーブレットコンテナによって生成される。
5. リクエストがあると、 `DelegateFilterProxy` が呼ばれ、自身の名前（`"springSecurityFilterChain"`）で Spring コンテナから Bean を取得する（`FilterChainProxy` が取得される）。
6. `FilterChainProxy` に処理が委譲される。

※分かりやすさ優先のため厳密には正しくないところもあります。雰囲気で見てください。

---
## Hello World を読み解く
1. <del>サーバー起動～リクエスト受付</del>
2. **Spring Security の入り口**
3. ログイン処理
4. アクセス制御
5. ログアウト処理

---
## SecurityFilterChain
- `<http>` が登録する重要なクラスの１つ
- 名前の通り、 `Filter` を Chain (連鎖)させている
- `FilterChainProxy` は、受け取ったリクエストを `SecurityFilterChain` が持つ `Filter` 達に委譲する
- デフォルトで登録される `Filter` の例
    - `SecurityContextPersistenceFilter`
    - `CsrfFilter`
    - `AnonymousAuthenticationFilter`
    - `ExceptionTranslationFilter`
    - `FilterSecurityInterceptor`
    - etc...

---
## Spring Security と Filter
![SpringSecurityとFilter.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/8c9c48c0-87ab-b887-eea6-663508980e35.jpeg)

- Spring Security は `Filter` の組み合わせで実現されている
- 機能ごとに `Filter` が用意されており、その `Filter` を `SecurityFilterChain` に登録するかどうかで機能の有効・無効を切り替えられる

---
## URL パターンごとの SecurityFilterChain
![patternごとのSecurityFilterChain.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/8963810a-b6fa-c81f-56e0-08a637c51fe3.jpeg)

- `SecurityFilterChain` は URL パターン単位で定義することができる
    - `/api/**` にマッチする URL へのアクセスの場合は REST API 用に設定した `SecurityFilterChain` を、
    - それ以外の URL (`/**`) へのアクセスは、通常の画面アクセス用に設定した `SecurityFilterChain` を使用する
    - といったことができる
- 例えば、「Form ログイン」のような機能は REST API でのアクセスでは不要になる

設定ファイルは次のようになる。

```xml:applicationContext.xml
  <sec:http pattern="/api/**">
    ...
  </sec:http>
  
  <sec:http pattern="/**">
    ...
  </sec:http>
```

- `<http>` タグの `pattern` 属性で指定する  
  （Ant 形式で指定できる）
- 設定は上から順番に適用されるので、より限定的な設定をしているものを上に持ってくる  
  （`/**` の設定が `/api/**` より上にあると、 `/api/**` へのアクセスが先に `/**` の設定にマッチしてしまう）

---
## まとめ <span class="h2">(Spring Security の入り口)</span>
- `<http>` は、 `SecurityFilterChain` を Bean として登録する
- `SecurityFilterChain` は、複数の `Filter` を保持している
- Spring Security は、機能ごとに `Filter` が用意されている
- `Filter` を組み合わせることで、必要な機能だけを持つ `SecurityFilterChain` を定義できる
- `SecurityFilterChain` は URL パターン単位で定義できるので、
    - REST API 用の `SecurityFilterChain`
    - 通常の画面アクセス用の `SecurityFilterChain`  
      という設定ができる

![patternごとのSecurityFilterChain.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/8963810a-b6fa-c81f-56e0-08a637c51fe3.jpeg)

---
## Hello World を読み解く
1. <del>サーバー起動～リクエスト受付</del>
2. <del>Spring Security の入り口</del>
3. **ログイン処理**
4. アクセス制御
5. ログアウト処理

---
## Form ログインの有効化
```xml:applicationContext.xml
  <sec:http>
    ...
    <sec:form-login />
    ...
  </sec:http>
```

- `<form-login>` タグを追加すると、 Form ログインが有効になる
- Form ログインで必要になる `Filter` が `SecurityFilterChain` に追加される

### デフォルトのログインページ
![spring-security.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/bd60e9d8-b68f-c24e-be2b-7274452bf848.jpeg)

- `<form-login>` の `login-page` 属性が指定されていない場合、 `DefaultLoginPageGeneratingFilter` という `Filter` が登録される
- `/login` に GET メソッドのリクエストがあると簡易なログインページを生成して返す
- 動作確認をサクッと試したい場合に便利
    - Remember-Me 認証を有効にしたら自動的に Remember-Me 用のチェックボックスが追加されたりする

---
## ログインリクエストの処理
- ログイン処理は `UsernamePasswordAuthenticationFilter` という `Filter` によって行われる
- `/login` に POST メソッドによるリクエストがくると、認証処理を開始する
- ただし、実際の認証処理は `AuthenticationManager` に委譲する

![AuthenticationManagerに委譲.png](https://qiita-image-store.s3.amazonaws.com/0/28302/a931be89-a401-2c07-3e27-7be0a06bfecc.png)

<span class="h1 text-danger">ここから「委譲」がたくさん出てきてややこしいので注意！</span>

---
## AuthenticationManager
```xml:applicationContext.xml
  <sec:authentication-manager> ★
    <sec:authentication-provider>
      <sec:user-service>
        <sec:user name="foo" ... />
        <sec:user name="bar" ... />
      </sec:user-service>
    </sec:authentication-provider>
  </sec:authentication-manager>
```

- `<authentication-manager>` タグを宣言することで、 `AuthenticationManager` の実装クラスである `ProviderManager` が Spring コンテナに登録される
- 認証処理の入り口となるクラス
- ただし、 `ProviderManager` 自身は認証処理は行わず、 `AuthenticationProvider` に**委譲**する

![ProviderManagerとAuthenticationProvider.png](https://qiita-image-store.s3.amazonaws.com/0/28302/03878d72-0957-5778-dc99-daeafc2526cd.png)


---
## ProviderManager と AuthenticationProvider
![AuthenticationProviderの実装クラス達.png](https://qiita-image-store.s3.amazonaws.com/0/28302/a08f7be6-5f31-15fd-e15a-98dc76f30fd0.png)

- `AuthenticationProvider` は、認証の種類ごとに多くの実装クラスが存在している
- `ProviderManager` はアプリケーションがサポートする認証方式に従い、複数の `AuthenticationProvider` のインスタンスを保持している
- それぞれの `AuthenticationProvider` に対して、現在の認証リクエストがサポート対象かどうかを判断させ、サポートしている場合に `AuthenticationProvider` による認証処理が行われる
- `ProviderManager` は、複数の `AuthenticationProvider` をまとめあげ管理する役割を担っている（まさに `ProviderManager`）

---
## パスワード認証を行う AuthenticationProvider
```xml:applicationContext.xml
  <sec:authentication-manager>
    <sec:authentication-provider> ★
      <sec:user-service>
        <sec:user name="foo" ... />
        <sec:user name="bar" ... />
      </sec:user-service>
    </sec:authentication-provider>
  </sec:authentication-manager>
```

- `<authentication-provider>` タグを使用することで、 `DaoAuthenticationProvider` というクラスが `AuthenticationProvider` の実装として登録される
- `DaoAuthenticationProvider` は、ユーザー名とパスワードの組み合わせを使った認証処理を行う
- その際、ユーザー情報の取得に Dao (Data Access Object) を使用する
- Dao に当たる部分は、 `UserDetailsService` に**委譲**する

![UserDetailsServiceに委譲.png](https://qiita-image-store.s3.amazonaws.com/0/28302/c8df2d00-5967-8a54-cdf8-27cad8f2359e.png)

---
## UserDetailsService
```java:UserDetailsService
public interface UserDetailsService {

    UserDetails
        loadUserByUsername(String username)
            throws UsernameNotFoundException;
}
```

- ユーザー名をもとにユーザー情報(`UserDetails`)を返すメソッドを持つ
- ユーザーが見つからなかった場合は `UsernameNotFoundException` をスローする
- Spring Security にはこのインターフェースを実装したクラスがいくつか用意されている

### InMemoryUserDetailsManager
メモリ内にユーザー情報を保存しておく実装

### JdbcUserDetailsManager
JDBC 経由でデータベースからユーザー情報を検索してくる実装

---
## InMemoryUserDetailsManager を使用する
```xml:applicationContext.xml
  <sec:authentication-manager>
    <sec:authentication-provider>
      <sec:user-service> ★
        <sec:user name="foo" ... />
        <sec:user name="bar" ... />
      </sec:user-service>
    </sec:authentication-provider>
  </sec:authentication-manager>
```

- `<user-service>` タグを使用すると、 `UserDetailsService` の実装として `InMemoryUserDetailsManager` が Spring コンテナに登録される

---
## UserDetails
```java:UserDetails.java
public interface UserDetails extends Serializable {

    String getUsername();
    String getPassword();
    Collection<? extends GrantedAuthority>
        getAuthorities();

    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
}
```

- `UserDetails` は、ログインユーザーの詳細情報を提供するインターフェース
- ユーザー名やパスワード、付与された権限のコレクション、期限切れやロックなどの状態を取得できる
- このインターフェースを実装したクラスとして、 `User` というクラスが用意されている

---
## User を使用する
```xml:applicationContext.xml
  <sec:authentication-manager>
    <sec:authentication-provider>
      <sec:user-service>
        <sec:user name="foo" ... /> ★
        <sec:user name="bar" ... /> ★
      </sec:user-service>
    </sec:authentication-provider>
  </sec:authentication-manager>
```

- `<user>` タグを使用すると、 `User` クラスを使ってユーザー情報が生成される
- 生成されたユーザー情報は、 `<user-service>` すなわち `InMemoryUserDetailsManager` に保存される

---
## ここまでの流れを整理
![ログイン処理整理.png](https://qiita-image-store.s3.amazonaws.com/0/28302/6fc313dd-5159-e2b5-a7d7-fcbfae528b21.png)

1. `/login` に POST リクエストがくると、 `UsernamePasswordAuthenticationFilter` が認証処理を行う
2. 認証処理は `AuthenticationManager` に委譲される
3. `AuthenticationManager` の実装クラスである `ProviderManager` は、所有する `AuthenticationProvider` に認証処理を委譲する
4. `<authentication-provider>` タグで登録された `DaoAuthenticationProvider` は、ユーザー名とパスワードをもとに認証処理を行う
5. その際、ユーザー情報の検索は `UserDetailsService` に委譲する
6. `<user-service>` タグで登録された `InMemoryUserDetailsManager` は、 `<user>` タグで定義されたユーザー情報（`UserDetails`）をメモリ上に保存している

上記の関係性が、下の設定に込められている。

```xml:applicationContext.xml
  <sec:authentication-manager>
    <sec:authentication-provider>
      <sec:user-service>
        <sec:user name="foo" ... />
        <sec:user name="bar" ... />
      </sec:user-service>
    </sec:authentication-provider>
  </sec:authentication-manager>
```


---
## 認証に成功したら
```java:AuthenticationProvider.java
public interface AuthenticationProvider {

    Authentication authenticate(Authentication authentication)
        throws AuthenticationException;
}
```

- `AuthenticationProvider` の認証処理が成功すると、ログインしたユーザーの情報を保持した `Authentication` オブジェクトが返却される
- この `Authentication` オブジェクトはセッションに保存され、次回以降のリクエストなどで参照されるようになる

---
## まとめ <span class="h2">(ログイン処理)</span>
- `<form-login>` で Form 認証が有効になる
- `UsernamePasswordAuthenticationFilter` が追加され、認証処理を開始する
- `AuthenticationManager` は認証処理を制御する
- `AuthenticationProvider` が具体的な認証処理を行う
- `UserDetailsService` はユーザー情報を検索する Dao
- `UserDetails` はユーザーの詳細情報を提供する
- 認証に成功すると、 `Authentication` オブジェクトがセッションに保存される

---
## Hello World を読み解く
1. <del>サーバー起動～リクエスト受付</del>
2. <del>Spring Security の入り口</del>
3. <del>ログイン処理</del>
4. **アクセス制御**
5. ログアウト処理

---
## FilterSecurityInterceptor
- `<http>` タグを定義することで追加される重要な `Filter` の１つ
- **セキュアオブジェクト**の処理を実行する前後に処理を挟む
- HTTP リクエストに関するアクセス制御は、この `FilterSecurityInterceptor` の中で開始される

### セキュアオブジェクト
- **セキュリティ保護対象** のことを、 Spring Security のリファレンスでは **セキュアオブジェクト（Secure Object）** と呼んでいる
- 「Object」とあるが、特定の Java オブジェクトのことではなく、「対象」という本来の Object の意味で使われている（たぶん）
- 「特定の URL への HTTP リクエスト」や「メソッドの実行」などがセキュアオブジェクトにあたる

---
## アクセス制御の委譲
![AccessDecisionManagerに委譲.png](https://qiita-image-store.s3.amazonaws.com/0/28302/6f1db999-f88b-6995-22c7-3f8998bc1c2d.png)

- `FilterSecurityInterceptor` 自身はアクセス制御についてのチェックは行わない
- アクセス制御の判定は `AccessDecisionManager` に委譲する

---
## 投票によるアクセス制御
![投票によるアクセス制御](https://qiita-image-store.s3.amazonaws.com/0/28302/a5fd3a66-0489-4908-26c9-b79568ec4249.png)

- Spring Security が提供する `AccessDecisionManager` の実装クラスは、「**投票**」によってアクセス制御を行う
- `AccessDecisionManager` は `AccessDecisionVoter` にアクセスの可否を投票させる
    - <font color="blue">付与：アクセス可</font>
    - <font color="red">拒否：アクセス不可</font>
    - <font color="gray">棄権：サポート対象外</font>
- `AccessDecisionManager` は、投票結果を集計して結論を出す
    - 可の場合は何もしない
    - 不可の場合は `AccessDeniedException` をスローする

---
## AccessDecisionManager の実装クラス
![AccessDecisionManagerと実装クラス.png](https://qiita-image-store.s3.amazonaws.com/0/28302/af45e692-61a9-c2cd-1959-f225959e2c83.png)

- `AccessDecisionManager` には実装クラスが３つ用意されている
- `AffirmativeBased`
    - 「付与」が１つでもあればアクセス可
    - デフォルトはこのクラスが使用される
- `ConsensusBased`
    - 「拒否」＜「付与」の場合はアクセス可
- `UnanimousBased`
    - 全て「付与」の場合はアクセス可

---
## 式ベースのアクセス制御
```xml:applicationContext.xml
  <sec:http>
    <sec:intercept-url
            pattern="/login"
            access="permitAll" />
    <sec:intercept-url
            pattern="/**"
            access="isAuthenticated() and hasAuthority('BAR')" />
    ...
  </sec:http>
```

- `AccessDecisionVoter` の実装には、デフォルトでは `WebExpressionVoter` が使用される
- `Expression` すなわち式ベースでアクセス可否を制御する
- アクセス制御の式は `<intercept-url>` タグの `access` 属性で指定する
- `pattern` 属性には、その制御を適用する URL のパターンを指定する

---
## Spring Expression Language (SpEL)
```java:applicationContext.xml
  access="permitAll"
  access="isAuthenticated() and hasAuthority('BAR')"
```

- アクセス制御の式には Spring Expression Language という Spring 独自の式言語を使用する
- 式を評価した結果が `boolean` になるようにする
    - `true` なら<font color="blue">アクセス可</font>
    - `false` なら<font color="red">アクセス不可</font>
- Spring Security 用に関数や定数が拡張されている
    - `permitAll`：常に `true`
    - `isAuthenticated()`：認証済みなら `true`
    - `hasAuthority()`：指定した権限を持っていれば `true`

---
## まとめ <span class="h2">(アクセス制御)</span>
- `FilterSecurityInterceptor` でアクセス制御が開始される
- 実際の制御は `AccessDecisionManager` が行う
- 標準の実装は「投票」によってアクセス可否を判定している
- `AccessDecisionVoter` が投票を行い、 `AccessDecisionManager` が投票結果を集計し結論を出す
- デフォルトでは `WebExpressionVoter` による SpEL を利用した式ベースのアクセス制御が有効になっている

---
## Hello World を読み解く
1. <del>サーバー起動～リクエスト受付</del>
2. <del>Spring Security の入り口</del>
3. <del>ログイン処理</del>
4. <del>アクセス制御</del>
5. **ログアウト処理**

---
## ログアウトを有効にする
```xml:applicationContext.xml
  <sec:http>
    ...
    <sec:logout />
  </sec:http>
```

- `<logout>` タグを使用すると、 `LogoutFilter` が追加される
- `/logout` に POST リクエストがくると、 `LogoutFilter` がログアウトの処理を行う

---
## 全体ふりかえり
### サーバー起動～リクエスト受付
![test](https://qiita-image-store.s3.amazonaws.com/0/28302/87384b9e-afc0-65ed-c6a5-2eefaddd39e3.png)

### Spring Security の入り口
![SpringSecurityとFilter.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/8c9c48c0-87ab-b887-eea6-663508980e35.jpeg)

### ログイン処理
![ログイン処理整理.png](https://qiita-image-store.s3.amazonaws.com/0/28302/6fc313dd-5159-e2b5-a7d7-fcbfae528b21.png)

### アクセス制御
![投票によるアクセス制御](https://qiita-image-store.s3.amazonaws.com/0/28302/a5fd3a66-0489-4908-26c9-b79568ec4249.png)

---
## これだけの意味がこの設定に込められている
```xml:web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  
  <filter>
    <filter-name>
      springSecurityFilterChain
    </filter-name>
    <filter-class>
      org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
  </filter>

  <filter-mapping>
    <filter-name>
      springSecurityFilterChain
    </filter-name>
    <url-pattern>
      /*
    </url-pattern>
  </filter-mapping>
</web-app>
```

```xml:applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       ...>

  <sec:http>
    <sec:intercept-url
            pattern="/login"
            access="permitAll" />
    <sec:intercept-url
            pattern="/**"
            access="isAuthenticated() and hasAuthority('BAR')" />
    <sec:form-login />
    <sec:logout />
  </sec:http>
  
  <sec:authentication-manager>
    <sec:authentication-provider>
      <sec:user-service>
        <sec:user name="foo"
                  password="foo"
                  authorities="" />
        <sec:user name="bar"
                  password="bar"
                  authorities="BAR" />
      </sec:user-service>
    </sec:authentication-provider>
  </sec:authentication-manager>
</beans>
```
---
## <span class="h1">いかがでしたでしょうか？<br>Spring Security 簡単そうと思いました？</span>

---
## 【悲報】まだ Hello World

---
## 今日話していない機能とか要素とか
- GrantedAuthority
- Role の階層化
- メソッドのセキュリティ
- CSRF
- Memember-Me
- パスワードエンコード
- 各種カスタマイズ方法
- テスト
- etc...

---
## <span class="h1">でも、今日の話が知識の下地としてできていれば<br>乗り越えられるはず（たぶん）</span>

---
### 以上