Help us understand the problem. What is going on with this article?

Spring SecurityでWeb APIのAPIキー認証を作成する

More than 1 year has passed since last update.

やりたいこと

Spring SecurityでWeb API呼び出し時などで、リクエスト毎にヘッダのAPIキーで認証する。

実現方法

  1. セッションを使用せずリクエスト毎に認証を実施する設定を行う
  2. リクエストヘッダ中のAPIキーによる認証処理を行うためのフィルター、サービスを作成する
  3. 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())
        }
    }
}
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away