Spring Securityの認証処理
はじめに
Spring Securityで認証を実装するときに、機能は実装できたけど、あまりスッキリしなかったので、アーキテクチャ含め勉強した。
この記事ではDB認証について記述するが、基本的には他の認証方法も同じような流れだと思われる。
アーキテクチャについて
SpringSecurityでDB認証を行う時のアーキテクチャは以下のようになっている。
- クライアントから送信されたリクエストをFilterChainProxyが受け取る
- FilterChainProxyがHttpFirewallインターフェースのメソッドを呼び出し、HttpServletRequestとHttpServletResponseに対してファイウォール機能を組み込む
- FilterChainProxyがSecurityFilterChainに設定されているSecurityFIlterクラスに処理を委譲する
- SecurityFilterChainに設定されているSecurityFilterの処理を順番に実行する
- AuthenticationFilterはリクエストから資格情報を取得し、AuthenticationManagerクラスの認証処理を呼び出す
- ProviderManager(AuthenticationFilterのデフォルトの実装クラス)はAuthenticationProviderの実装クラスに実際の認証処理を移譲する(この例ではDaoAuthenticationProviderがそれにあたる)
- DaoAuthenticationProviderはUserDetailsServiceのユーザー情報取得処理を呼び出す
- UserDetailsServiceの実装クラス(この例ではMyUserDetailsService)はDBからユーザー情報を取得する
- MyUserDetailsServiceはDBから取得したユーザー情報からUserDetailsの実装クラス(ここではMyUserDetails)を生成する
- DaoAuthenticationProviderはUserDetailsServiceから返却されたUserDetailsとクライアントが指定した認証情報との照合を行い、クライアントが指定したユーザーの正当性をチェックする
実際のコード
上の処理の中で、実際に記述する必要があるのはMyUserDetailsServiceとMyUserDetails、それとConfigクラスである。
MyUserDetails
MyUserDetailsはUserDetailsインターフェースを実装して作成する。
MyUserDetailsは別で定義したAccountとGrantedAuthorityのCollectionをプロパティに保持している。
これらの値はMyUserDetailsServiceがMyUserDetailsを生成する時に設定する値となる。
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
}
}
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からこのクラスを呼び出す処理を書く必要がなくなる。
@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定義する。
@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認証が実現できる。
参考文献