Spring Security の仕組みを整理してみた

この記事は[ラクスアドベントカレンダー](https://qiita.com/advent-calendar/2021/rakus)の17日目の記事です。



## はじめに

最近Spring Securityを触る機会があったのですが、「設定クラスを書けばなんかできるけど、内部的にどんな動きをしてるかよくわからない」というものが正直な感想でした。

ということで今回は「Spring Securityがやっていること」についてまとめていきたいと思います。

## Spring Securityとは

Spring Securityは、Springベースのアプリケーションに、認証 (BASIC認証、OpenID認証など）、認可 (権限によるアクセス許可、OAuth 2.0など)、その他多数のセキュリティ対策を可能にするフレームワークです。

## Spring Securityの仕組み

Spring Securityはサーブレットフィルタの仕組みを使用して各種セキュリティ機能を実現しています。
絵にすると以下のようなイメージです。
![SpringSecurity.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195788/b7133a98-b184-ca6a-e124-b65c2c559f33.png)

1. クライアントによるWebアプリケーションへのリクエスト
1. Spring SecurityのFilterChainProxyクラスがクライアントのリクエストを受け取り、HttpFirewallインタフェースのメソッドを呼び出してHttpServletRequestとHttpServletResponseに対してファイアフォール機能を組み込む。
1. Security Filterクラスに処理を委譲する。
1. 各フィルタの処理が正常に終了すると後続のサーブレットフィルタが呼び出される。
1. すべてのフィルタ処理が正常終了した後、Webアプリケーション内のリソースへアクセスする。
1. Webアプリケーションからのレスポンス。

#### [FilterChainProxy](https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/web/FilterChainProxy.html)
ユーザからのリクエストを最初に受けるクラスでFilterChainProxy自身はセキュリティ機能を持ちませんが、各種セキュリティ機能の流れをコントロールします。

#### [HttpFirewall](https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/web/firewall/HttpFirewall.html)
ファイアフォール機能を組み込むためのインターフェースです。
クライアントから受信したリクエストの検証や、サポートしていないリクエストを拒否するといった設定が可能です。

#### [SecurityFilterChain](https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/web/SecurityFilterChain.html)
クライアントから受信したリクエストに対して適用するSecurityFilterを管理するためのインタフェースです。


###### SecurityFilter
SecurityFilterを適用することで、リクエストごとに必要なセキュリティ対策を実現することが可能です。

## サンプルコードで確認してみる
大枠の概要はとらえられましたが、コードと照らし合わせてみないと何ともしっくりきませんでした。
ということで、Spring Securityの設定クラスの実装を確認していきます。



```SecurityConfig.java
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
			.antMatchers("/css/**", "/js/**").permitAll()
			.antMatchers("/admin/**").hasAuthority("ROLE_ADMIN")
			.anyRequest().authenticated();
    }
}
```

`@EnableWebSecurity`をつけることでSpring Securityが有効になり、後続のフィルタ処理が行われるようになりますが、いまいち上述の絵とつながりません。
`@EnableWebSecurity`にヒントがありそうなので実装を確認していきます。

```EnableWebSecurity.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
		HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
	boolean debug() default false;

}
```
いくつかの設定クラスを読み込んでいるようですね。
その中の１つである[WebSecurityConfiguration](https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.html)のドキュメントを見てみると、、、

> WebSecurity を使用して、Spring Security の Web ベースのセキュリティを実行する FilterChainProxy を作成します。次に、必要な Bean をエクスポートします。WebSecurityConfigurerAdapter を継承して Configuration として公開するか、WebSecurityConfigurer を実装して Configuration として公開することにより、WebSecurity をカスタマイズできます。この構成は、EnableWebSecurity を使用するときにインポートされます。

ここで**FilterChainProxy**が登場してきました。
つまり、`@EnableWebSecurity`を付与することで FilterChainProxy　が生成され、絵の構成が作られることでSpring Security の機能が有効になるという仕組みのようです。

次は、`SecurityConfig.java`に戻り、各種セキュリティ対策（≒SecurityFilterChain）がどのように実現されているのかを確認してみます。
`SecurityConfig.java`は[WebSecurityConfigurerAdapter](https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.html)を継承することで SecurityFilterChain を構築することができるようになっています。
`WebSecurityConfigurerAdapter`のconfigureメソッドをオーバライドすることでSecurityFilterChainをカスタムし、任意の認証機能を持ったSecurityFilterChainや、特定のリクエストに制限を持たせるSecurityFilterChainを構築することができます。

ここまでに確認したことを`SecurityConfig.java`に反映するとこんな感じです。

```SecurityConfig.java
@EnableWebSecurity // Spring Securityの有効化。FilterChainProxyの構築
public class SecurityConfig extends WebSecurityConfigurerAdapter { // SecurityFilterChainの構築

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
			.antMatchers("/css/**", "/js/**").permitAll() 
			.antMatchers("/admin/**").hasAuthority("ROLE_ADMIN")
			.anyRequest().authenticated();
        //各フィルタのカスタマイズ
    }
}
```


## まとめ
ということで、Spring Securityが何をやっているのかを紐解いていきました。処理の流れと、実際のソースを比較してみると思いのほか複雑ではない印象を受けました。
Spring Securityにセキュリティ対策を任せることで、自前で実装をしなくても良かったり、アプリケーション本体は業務に集中することができたりと、恩恵を受けることができますので上手に使っていきたいところです。

引き続き[ラクスアドベントカレンダー](https://qiita.com/advent-calendar/2021/rakus)お楽しみください。

## 参考


* https://spring.pleiades.io/
* https://terasolunaorg.github.io/guideline/5.1.0.RELEASE/ja/Security/SpringSecurity.html
* https://qiita.com/opengl-8080/items/c105152c9ca48509bd0c