SpringSecurityの認証処理

# Spring Securityの認証処理

## はじめに

Spring Securityで認証を実装するときに、機能は実装できたけど、あまりスッキリしなかったので、アーキテクチャ含め勉強した。

この記事ではDB認証について記述するが、基本的には他の認証方法も同じような流れだと思われる。

## アーキテクチャについて

SpringSecurityでDB認証を行う時のアーキテクチャは以下のようになっている。

![Spring Security.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3077686/2c9d66cc-7c7b-424a-91a5-623c1beed3e8.png)

1. クライアントから送信されたリクエストをFilterChainProxyが受け取る
2. FilterChainProxyがHttpFirewallインターフェースのメソッドを呼び出し、HttpServletRequestとHttpServletResponseに対してファイウォール機能を組み込む
3. FilterChainProxyがSecurityFilterChainに設定されているSecurityFIlterクラスに処理を委譲する
4. SecurityFilterChainに設定されているSecurityFilterの処理を順番に実行する
5. AuthenticationFilterはリクエストから資格情報を取得し、AuthenticationManagerクラスの認証処理を呼び出す
6. ProviderManager(AuthenticationFilterのデフォルトの実装クラス)はAuthenticationProviderの実装クラスに実際の認証処理を移譲する(この例ではDaoAuthenticationProviderがそれにあたる)
7. DaoAuthenticationProviderはUserDetailsServiceのユーザー情報取得処理を呼び出す
8. UserDetailsServiceの実装クラス(この例ではMyUserDetailsService)はDBからユーザー情報を取得する
9. MyUserDetailsServiceはDBから取得したユーザー情報からUserDetailsの実装クラス(ここではMyUserDetails)を生成する
10. DaoAuthenticationProviderはUserDetailsServiceから返却されたUserDetailsとクライアントが指定した認証情報との照合を行い、クライアントが指定したユーザーの正当性をチェックする

## 実際のコード

上の処理の中で、実際に記述する必要があるのはMyUserDetailsServiceとMyUserDetails、それとConfigクラスである。

### MyUserDetails

MyUserDetailsはUserDetailsインターフェースを実装して作成する。
MyUserDetailsは別で定義したAccountとGrantedAuthorityのCollectionをプロパティに保持している。

これらの値はMyUserDetailsServiceがMyUserDetailsを生成する時に設定する値となる。

```kotlin
class MyUserDetails(account: Account, authorities: Collection<GrantedAuthority>) : UserDetails {
    val account: Account
    private val authorities: Collection<GrantedAuthority>

    init {
        this.account = account
        this.authorities = authorities
    }

    override fun getPassword(): String {
        return this.account.password
    }

    override fun getUsername(): String {
        return this.account.userName
    }

    override fun isEnabled(): Boolean {
        return this.account.isEnabled
    }

    override fun getAuthorities(): Collection<GrantedAuthority> {
        return this.authorities
    }

    fun getNickName(): String {
        return this.account.nickName
    }
}
```

```kotlin
data class Account (
    val userName: String,
    val password: String,
    val nickName: String,
    val isEnabled: Boolean
)
```

### MyUserDetailsService

MyUserDetailsServiceでは、DBから取得した情報を基に、MyUserDetailsを作成する処理を記述する。

UserDetailsServiceインターフェースで定義されているloadUserByUsernameメソッドをオーバーライドし、クライアントから渡されたusernameを元にMyUserDetailsを生成する処理を記述している。

AuthorityUtilsはSpringSecurityで提供されているGrantedAuthorityコレクションを操作するためのユーティリティメソッドである。
今回は全ユーザーが`ADMIN`権限を持っていることにする。

MyUserDetailsに`@Service`を付与することでDIコンテナに登録されるため、DaoAuthenticationProviderからこのクラスを呼び出す処理を書く必要がなくなる。

```kotlin
@Service
class MyUserDetailsService(
    val accountRepository: AccountRepository
) : UserDetailsService {
    override fun loadUserByUsername(username: String?): UserDetails {
        val accountEntity = accountRepository.findByUserName(username)
        val account = Account(
            userName = accountEntity.userName,
            password = accountEntity.password,
            nickName = accountEntity.nickName,
            isEnabled = accountEntity.isEnabled
        )
        val authorities = getAuthorities(account)

        return MyUserDetails(account, authorities)
    }

    private fun getAuthorities(account: Account): Collection<GrantedAuthority> {
        return AuthorityUtils.createAuthorityList("ADMIN")
    }
}
```

### SecurityConfig

Configクラスに`@EnableWebSecurity`を付与することでSpringSecurityを有効化してくれる。

昔は`@Enable*`に`@Configuration`が付与されていたため`@Configuration`を付与しなくてもJava Configクラスと認識されていたが、SpringSecurity6.0から削除されたので、`@Configuration`を付与する必要がある。

SecurityFilterChainは`@Bean`を付与し、Bean定義する。

```kotlin
@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.securityMatcher("/**")
            .formLogin {
            login -> login
                .loginPage("/login")
                .defaultSuccessUrl("/")
                .failureUrl("/login/error")
                .permitAll()
        }.authorizeHttpRequests{
            auth -> auth
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                .anyRequest().authenticated()
        }
        return http.build()
    }

    @Bean
    fun passwordEncoder(): PasswordEncoder {
        return BCryptPasswordEncoder()
    }
}
```

これらのコードを実装すると、DB認証が実現できる。

## 参考文献

https://www.shoeisha.co.jp/book/detail/9784798142470

https://spring.pleiades.io/spring-security/reference/servlet/architecture.html

https://terasolunaorg.github.io/guideline/5.7.2.RELEASE/ja/Security/SpringSecurity.html

https://qiita.com/suke_masa/items/908805dd45df08ba28d8#spring-security-54-securityfilterchain-%E3%82%92bean%E5%AE%9A%E7%BE%A9%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F