やりたいこと
Spring SecurityでWeb API呼び出し時などで、リクエスト毎にヘッダのAPIキーで認証する。
Javaによる例は個人ブログの記事【Spring】Spring Securityを使ってWeb APIの認可を行うに載せています。この記事では@PreAuthorizeによる権限チェックの例も載せています。
実現方法
- セッションを使用せずリクエスト毎に認証を実施する設定を行う
- リクエストヘッダ中のAPIキーによる認証処理を行うためのフィルター、サービスを作成する
- 2.で作成したフィルター、サービスを登録
1.リクエスト毎に認証を実施する設定
WebSecurityConfigurerAdapterのconfigureメソッドでセッションのポリシーをステートレスに設定する。
具体的には以下のようにSessionCreationPolicy.STATELESSを設定する。
@Configuration
@EnableWebSecurity
class SecurityConfig: WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity?) {
http
?.authorizeRequests()
?.antMatchers("/hello/**")?.hasAnyRole("USER", "ADMIN")
?.antMatchers("/admin/**")?.hasRole("ADMIN")
?.anyRequest()
?.authenticated()
?.and()
?.addFilter(preAuthenticatedProcessingFilter())
?.exceptionHandling()
?.authenticationEntryPoint(http401UnauthorizedEntryPoint())
// リクエスト毎に認証を実施
http?.sessionManagement()?.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
....
2. リクエストヘッダ中のAPIキーによる認証処理を作成
Spring SecurityのPre-Authenticationを使用する。AbstractPreAuthenticatedProcessingFilterとAuthenticationUserDetailsServiceを使用しリクエストヘッダーからAPIキーを取得するフィルターとAPIキーによる認証を行うサービスを作成する。
フィルターの作成
AbstractPreAuthenticatedProcessingFilterを継承したクラスを作成し、getPreAuthenticatedCredentialsメソッドにリクエストヘッダ中のAPIキーを取り出す処理を作成する。
class MyPreAuthenticatedProcessingFilter: AbstractPreAuthenticatedProcessingFilter() {
// APIキーを取得
override fun getPreAuthenticatedCredentials(request: HttpServletRequest?): Any {
return request?.getHeader("X-Auth-Token") ?: ""
}
override fun getPreAuthenticatedPrincipal(request: HttpServletRequest?): Any {
return ""
}
}
サービスの作成
以下のようにAuthenticationUserDetailsServiceを継承したクラスを作成し、取得したAPIキーによる認証処理を作成する。
型パラメータにはPreAuthenticatedAuthenticationTokenを指定する。
ここでは、テストのため適当な処理を書いているが、外部APIやDBを使ってAPIキーの検証する処理を書く。
class MyAuthenticationUserDetailService: AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
override fun loadUserDetails(token: PreAuthenticatedAuthenticationToken?): UserDetails {
// MyPreAuthenticatedProcessingFilterで取得したAPIキー
val credential = token?.credentials
if (credential.toString() == "") {
throw UsernameNotFoundException("User not found")
}
return when (credential) {
"token1" -> User("user", "", AuthorityUtils.createAuthorityList("ROLE_USER"))
"token2" -> User("admin", "", AuthorityUtils.createAuthorityList("ROLE_ADMIN"))
else -> User("user", "", AuthorityUtils.createAuthorityList("ROLE_INVALID") )
}
}
}
3. フィルター、サービスを登録
作成したフィルータ、サービスがDIされるように以下のように Bean定義を追加する。
@Configuration
@EnableWebSecurity
class SecurityConfig: WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity?) {
http
?.authorizeRequests()
?.antMatchers("/hello/**")?.hasAnyRole("USER", "ADMIN")
?.antMatchers("/admin/**")?.hasRole("ADMIN")
?.anyRequest()
?.authenticated()
?.and()
?.addFilter(preAuthenticatedProcessingFilter())
http?.sessionManagement()?.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
// サービス
@Bean
fun authenticationUserDetailsService(): AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
return MyAuthenticationUserDetailService()
}
// フィルター登録
@Bean
fun preAuthenticatedAuthenticationProvider(): PreAuthenticatedAuthenticationProvider {
return PreAuthenticatedAuthenticationProvider().apply {
setPreAuthenticatedUserDetailsService(authenticationUserDetailsService())
setUserDetailsChecker(AccountStatusUserDetailsChecker())
}
}
// フィルター
@Bean
fun preAuthenticatedProcessingFilter(): AbstractPreAuthenticatedProcessingFilter {
return MyPreAuthenticatedProcessingFilter().apply {
setAuthenticationManager(authenticationManager())
}
}
}