Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
32
Help us understand the problem. What is going on with this article?
@fanfanta

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

やりたいこと

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

Javaによる例は個人ブログの記事【Spring】Spring Securityを使ってWeb APIの認可を行うに載せています。この記事では@PreAuthorizeによる権限チェックの例も載せています。

実現方法

  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())
        }
    }
}
32
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  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
fanfanta
Sierで主にサーバサイドエンジニアをやっています。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
32
Help us understand the problem. What is going on with this article?