0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring SecurityのOpenID Connectの設定方法

Last updated at Posted at 2024-12-27

はじめに

Spring Securityを使っているアプリで、OpenID Connect(以下OIDC)を別のものに移行する機会があり、その際にOIDC周りの設定について見直しました。そのときに得られてた知見のまとめです。

OIDCの仕様についてはここでは特に解説はしません。
Auth屋さんのスライドや書籍が参考になりますので、気になる方はこちらもどうぞ。

まとめ

項目 概要
application.yamlでの設定できること OIDCの接続などの基本的なことの設定
JWTの署名のアルゴリズム ID tokenの署名のアルゴリズム
SecurityFilterChain セキュリテイの設定をするためのオブジェクト
OIDC user service ID tokenからログインユーザを生成する部分
Authentication success handler 認証処理が成功したときの処理
Authentivation failure handler 認証処理が失敗したときの処理

application.ymlで設定できること

yamlから設定できる内容の詳細は公式ドキュメントあります。

ほとんどの場合、OIDCの認可コードフローを使うので、その場合は下記のようになります。

application.yaml
spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: authorization_code
            redirect-url: {baseUrl}/login/oauth2/code/{registrationId}
            scope:
              - openid
              - profile
        provider:
          okta:	
            authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
            token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
            user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
            user-name-attribute: sub
            jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys

ログイン方法を複数提供したい場合は、registartion,providerの組を、複数設定できます。

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
             ...
          google:
             ...
         provider:
            okta:
               ...
            google:   

well-known endpoint

OIDCの仕様で、/.well-known/openid-configuration から取得できます。

例としてyahooのものを一部抜粋したものをあげます。
https://auth.login.yahoo.co.jp/yconnect/v2/.well-known/openid-configuration

{
  "issuer": "https://auth.login.yahoo.co.jp/yconnect/v2",
  "authorization_endpoint": "https://auth.login.yahoo.co.jp/yconnect/v2/authorization",
  "token_endpoint": "https://auth.login.yahoo.co.jp/yconnect/v2/token",
  "userinfo_endpoint": "https://userinfo.yahooapis.jp/yconnect/v2/attribute",
  "jwks_uri": "https://auth.login.yahoo.co.jp/yconnect/v2/jwks",
}  

ここにあるissuerのURLを、providerのissuer-uriを設定すれば、このwell-known endpointから取得して、必要な設定をしてくれます。provider側の設定を減らすことができます。

provider:
   yahoo:
     issuer-uri: https://auth.login.yahoo.co.jp/yconnect/v2

また、well-known endpointがend_session_endpointをもっている場合、OidcClientInitiatedLogoutSuccessHandlerを設定することで、OIDC側も含めてログアウト処理を実装できます。

注意するのは、issuer-uriにアクセスできない場合は、このやり方はつかえません。
spring securityのissuer-uriへのアクセスはRestTemplateが使われていますが、隠蔽されているので、この部分だけにproxyを適用することが難しいです。

max_age

OIDC側の仕様で、ログインしてから再認証が必要になる経過時間を設定できます。

max_age
OPTIONAL. Authentication Age の最大値. End-User が OP によって明示的に認証されてからの経過時間の最大許容値 (秒). もし経過時間がこの値より大きい場合, OP は End-User を明示的に再認証しなければならない (MUST). (max_age リクエストパラメータは OpenID 2.0 PAPE [OpenID.PAPE] の max_auth_age リクエストパラメータに相当する) max_age が指定された場合, 発行される ID Token は auth_time Claim を含まねばならない (MUST).

この仕組みによって、自分たちのアプリのセッションタイムアウトまでの時間を制御できます。
一番簡単な使い方は、authorization-uriでの指定です。

provider:
   yahoo:
     authorization-url: https://auth.login.yahoo.co.jp/yconnect/v2/authorization?max_age=3600

prompt

OIDCの仕様で、再認証やアカウントの切り替え・確認を要求するためのパラメタです。
prompt=loginとすれば、認証を再度要求できます。決済やアカウント情報の変更など、高いセキュリテイが要求される場面で使えます。

prompt
OPTIONAL. Authorization Server が End-User に再認証および同意を再度要求するかどうか指定するための, スペース区切りの ASCII 文字列のリスト. 以下の値が定義されている.
login
Authorization Server は End-User を再認証するべきである (SHOULD). 再認証が不可能な場合はエラーを返す (MUST). 典型的なエラーコードは login_required である.

こちらも、authorization-uriでの設定が一番簡単なやり方です。

provider:
   yahoo:
     authorization-url: https://auth.login.yahoo.co.jp/yconnect/v2/authorization?prompt=login

JWTの署名のアルゴリズム

ID tokenは署名付きJWT(JWS)で、署名のアルゴリズムの設定方法です。
ECDSAでキーペアをつくることが標準になりつつあるので、ES256を設定したい場合は下記のようにすればいいです。

@Bean
JwtDecoderFactory<ClientRegistartion> decoderFactory() {
   OidcIdTokenDecoderFactory decoderFactory = new OidcIdTokenFactory();
   decoderFactory.setJwsAlgorithmResolver(clientRegistration -> SignatureAlgorithm.ES256);
   return decoderFactory;
}

SecurityFilterChain

requestに対して、どのようなセキュリテイの処理をするかを決められるオブジェクトです。

OIDCの場合、下記のようになります。

@Bean
SecurityFilterChain oidc(HttpSecurity http,
        SampleUserService userService,
        SampleAuthenticationSuccessHandler successHandler,
        SampleAuthenticationFailuerHandler failureHandler) {
   http.authorizeHttpRequest(authz -> 
           authz.anyRequest().authenticated())
        .oauth2Login(login -> login
             .userInfoEndPoint(userinfo -> userinfo.oidcUserService(userService))
             .successHandler(successHandler)
             .failureHandler(failureHandler)
         ); 
    return http.build();
}

URLごとにセキュリティの設定を変えたい場合は、HttpSecurity#securityMatcherなどで、URLパターンを指定すればいいです。

http.securityMatcher("/okta/**")
       ....;

http.securityMatcher("/google/**") 
       ...;

OIDC user service

ID tokenからログインユーザを生成する部分です。Spring Securityでデフォルトの挙動を実現するためのサービスクラスがあります。ID tokenに加えて、自分たちのDBの情報も付け加えるなど、追加の処理がいる場合は、OAuth2UserService<OidcUserRequest, OidcUser> を実装したクラスを作成すればいいです。また、ログインユーザを表すオブジェクトは、OidcUserを実装していればよいです。

デフォルトで問題ない場合でも、委譲の形でラップしておくとその後に変更があっても対応しやすいです。

public class SampleOidcUser implements OidcUser, Serializable {

   private final OidcUser oidcUser;

   public SampleOidcUser(OidcUser oidcUser) {
      this.oidcUser = oidcUser;
   }

   ...

   // OidcUserに必要な実装はthis.oidcUserのものをそのまま使う

   @Override
   public Map<String, Object> getAttributes() {
       return oidcUser.getAttributes();
   }

   ...

}
// DIしやすくするためBeanに登録
@Bean
OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
    return new OidcUserService();
}
@Service
public class SampleUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {

   private final OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;

   // 実際にはOidcUserServiceのインスタンスを使う想定
   // 変更に強くするために、型の指定はゆるめている
   public SampleUserService(OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService) {
         this.oidcUserService = oidcService;
   }

   @Override
   public OidcUser loadUser(OidcUserRequest userRequest) {
       // ID tokenを取得して、OidcUserを作成
       OidcUser oidcUser = oidcUserService.loadUser(userRequest);
       // 必要ならここで追加処理をいれる
       ...
       // 作成したログインユーザクラスを返す
       return new SampleOidcUser(oidcUser);
   }
   
}

Authentication success handler

認証成功後の処理です。Spring securityのデフォルトの挙動では、認証前に最初にアクセスしたURLへリダイレクトします。

SavedRequestAwareAuthenticationSuccessHandlerがそのためのhandlerです。

この挙動を維持したまま、追加の処理をいれたい場合、委譲の形でAuthticationSuccessHandlerを実装するといいでしょう。

public class SampleAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final SavedRequestAwareAuthenticationSuccessHandler delegatedHandler;

    ...

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        // 必要ならここに追加処理をいれる 
        delegatedHandler.onAuthentication(request, response, authentication);
    }

}

RequestCache

SavedRequestAwareAuthenticationSuccessHandlerは、RequestCacheに最初のrequestを保持しています。
この保持したものをつかって、最初のURLへのリダイレクトを実現しています。そこの設定を変えたい場合は、下記のようにします。

@Bean
RequestCache requetCache() {
   HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
   requestCache.setSessionAttrName("sample_saved_request")
   return requetCache; 
}

@Bean
SavedRequestAwareAuthenticationSuccessHandler(RequestCache requestCache) {
   SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
   handler.setRequestCache(requestCache);
   return handler;
}

Authentication failure handler

認証に失敗したときの処理です。認証処理中に、AuthenticationExceptionが投げられると、このhandlerに入ってきます。自分たちの処理で例外をなげてfailure handlerでまとめて処理させたい場合は、AuthenticationExceptionや、それを継承した例外を投げればいいです。
ただし、Success handler内でAuthenticationExceptionを投げても、failure handlerには入ってこないです。ログインページや、専用のエラーページにリダイレクトさせることが多いです。

SimpleUrlAuthenticationFailureHandlerを使えば簡単にできます。

このクラスを継承してもいいですが、委譲とDIの形にしておくと変更やテストがやりやすいです。

public class SampleAuthenticationFailureHandler implements AuthenticationFailureHandler {

   private final SimpleUrlAuthenticationFailureHandler delegatedHandler;

   ...

   
  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
      // 必要ならここに追加の処理をいれる
      delegatedHandler.onAuthenticationFailure(request, response, exception);
  }
  
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?