OAuth 2.0 ログイン機能は、OAuth 2.0 プロバイダー(例: GitHub)または OpenID Connect 1.0 プロバイダー(Google など)の既存のアカウントを使用してユーザーにアプリケーションにログインさせる機能をアプリケーションに提供します。OAuth 2.0 Login は、「Google でログイン」または「GitHub でログイン」というユースケースを実装しています。
![]() | メモ |
---|---|
OZZ 2.0 Login は、OAuth 2.0 認証フレームワーク (英語) および OpenID Connect コア 1.0 (英語) で指定されている 認証コード付与を使用して実装されます。 |
Spring Boot 2.x は、OAuth 2.0 ログインの完全な自動構成機能を提供します。
このセクションでは、 Google を 認証プロバイダーとして使用して OAuth 2.0 ログインサンプルを構成する方法を示し、次のトピックを扱います。
ログインに Google の OAuth 2.0 認証システムを使用するには、Google API Console でプロジェクトを設定して OAuth 2.0 認証情報を取得する必要があります。
![]() | メモ |
---|---|
認証用の Google の OAuth 2.0 の実装 (英語) は OpenID Connect 1.0 (英語) 仕様に準拠しており、OpenID 認定 (英語) です。 |
「OAuth 2.0 のセットアップ」セクションから始まる OpenID Connect (英語) ページの指示に従ってください。
「OAuth 2.0 資格情報の取得」の手順を完了すると、クライアント ID とクライアントシークレットで構成される資格情報を持つ新しい OAuth クライアントが必要になります。
リダイレクト URI は、エンドユーザーのユーザーエージェントが Google で認証 され、同意ページで OAuth クライアント (前の手順で作成された) へのアクセスを許可した後にリダイレクトされるアプリケーション内のパスです。
「リダイレクト URI の設定」サブセクションで、 承認されたリダイレクト URI フィールドが http://localhost:8080/login/oauth2/code/google
に設定されていることを確認します。
![]() | ヒント |
---|---|
デフォルトのリダイレクト URI テンプレートは |
![]() | 重要 |
---|---|
OAuth クライアントがプロキシサーバーの背後で実行されている場合は、プロキシサーバー構成をチェックして、アプリケーションが正しく構成されていることを確認することをお勧めします。また、 |
Google で新しい OAuth クライアントを作成したため、 認証フローに OAuth クライアントを使用するようにアプリケーションを構成する必要があります。そうするには:
application.yml
に移動して、次の構成を設定します。
spring: security: oauth2: client: registration:google:
client-id: google-client-id client-secret: google-client-secret
例 12.1: OAuth クライアントのプロパティ
| |
ベースプロパティプレフィックスの後には、google などの ClientRegistration の ID が続きます。 |
client-id
および client-secret
プロパティの値を、前に作成した OAuth 2.0 資格情報に置き換えます。Spring Boot 2.x サンプルを起動し、 http://localhost:8080
に移動します。次に、Google へのリンクを表示するデフォルトの 自動生成されたログインページにリダイレクトされます。
Google リンクをクリックすると、認証のために Google にリダイレクトされます。
Google アカウントの認証情報で認証した後、表示される次のページは同意画面です。同意画面では、以前に作成した OAuth クライアントへのアクセスを認可または拒否するように求められます。 許可するをクリックして、OAuth クライアントがメールアドレスと基本的なプロファイル情報にアクセスすることを認可します。
この時点で、OAuth クライアントは UserInfo エンドポイント (英語) からメールアドレスと基本プロファイル情報を取得し、認証済みセッションを確立します。
次の表に、Spring Boot 2.x OAuth クライアントプロパティの ClientRegistration プロパティへのマッピングの概要を示します。
Spring Boot 2.x | ClientRegistration |
---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
![]() | ヒント |
---|---|
|
CommonOAuth2Provider
は、多数の有名なプロバイダー(Google、GitHub、Facebook、Okta)の一連のデフォルトクライアントプロパティを事前に定義しています。
例: authorization-uri
、 token-uri
、 user-info-uri
は、プロバイダーでは頻繁に変更されません。必要な構成を減らすためにデフォルト値を提供することは理にかなっています。
前に示した ように、Google クライアントを構成したときは、 client-id
プロパティと client-secret
プロパティのみが必要です。
次のリストに例を示します。
spring: security: oauth2: client: registration: google: client-id: google-client-id client-secret: google-client-secret
![]() | ヒント |
---|---|
|
google-login
など、別の registrationId
を指定する場合は、 provider
プロパティを構成することにより、クライアントプロパティの自動デフォルト設定を活用できます。
次のリストに例を示します。
spring: security: oauth2: client: registration: google-login:provider: google
client-id: google-client-id client-secret: google-client-secret
マルチテナンシーをサポートする OAuth 2.0 プロバイダーがいくつかあります。これにより、テナント(またはサブドメイン)ごとに異なるプロトコルエンドポイントが作成されます。
例: Okta に登録された OAuth クライアントは特定のサブドメインに割り当てられ、独自のプロトコルエンドポイントを持ちます。
これらの場合のために、Spring Boot 2.x はカスタムプロバイダープロパティを構成するための次の基本プロパティを提供します: spring.security.oauth2.client.provider. [providerId]
。
次のリストに例を示します。
spring: security: oauth2: client: registration: okta: client-id: okta-client-id client-secret: okta-client-secret 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
OAuth クライアントをサポートするための Spring Boot 2.x 自動構成クラスは OAuth2ClientAutoConfiguration
です。
次のタスクを実行します。
ClientRegistration
で構成される ClientRegistrationRepository
@Bean
を登録します。 WebSecurityConfigurerAdapter
@Configuration
を提供し、 httpSecurity.oauth2Login()
を介した OAuth 2.0 ログインを有効にします。特定の要件に基づいて自動構成をオーバーライドする必要がある場合、次の方法でオーバーライドできます。
次の例は、 ClientRegistrationRepository
@Bean
を登録する方法を示しています。
@Configuration public class OAuth2LoginConfig { @Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository(this.googleClientRegistration()); } private ClientRegistration googleClientRegistration() { return ClientRegistration.withRegistrationId("google") .clientId("google-client-id") .clientSecret("google-client-secret") .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}") .scope("openid", "profile", "email", "address", "phone") .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth") .tokenUri("https://www.googleapis.com/oauth2/v4/token") .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo") .userNameAttributeName(IdTokenClaimNames.SUB) .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs") .clientName("Google") .build(); } }
次の例は、 WebSecurityConfigurerAdapter
に @EnableWebSecurity
を提供し、 httpSecurity.oauth2Login()
を介して OAuth 2.0 ログインを有効にする方法を示しています。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests .anyRequest().authenticated() ) .oauth2Login(withDefaults()); } }
次の例は、 ClientRegistrationRepository
@Bean
を登録し、 WebSecurityConfigurerAdapter
を提供することにより、自動構成を完全にオーバーライドする方法を示しています。
@Configuration public class OAuth2LoginConfig { @EnableWebSecurity public static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests .anyRequest().authenticated() ) .oauth2Login(withDefaults()); } } @Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository(this.googleClientRegistration()); } private ClientRegistration googleClientRegistration() { return ClientRegistration.withRegistrationId("google") .clientId("google-client-id") .clientSecret("google-client-secret") .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}") .scope("openid", "profile", "email", "address", "phone") .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth") .tokenUri("https://www.googleapis.com/oauth2/v4/token") .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo") .userNameAttributeName(IdTokenClaimNames.SUB) .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs") .clientName("Google") .build(); } }
Spring Boot 2.x を使用できず、 CommonOAuth2Provider
で事前定義されたプロバイダーの 1 つ(Google など)を構成する場合は、次の構成を適用します。
@Configuration public class OAuth2LoginConfig { @EnableWebSecurity public static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests .anyRequest().authenticated() ) .oauth2Login(withDefaults()); } } @Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository(this.googleClientRegistration()); } @Bean public OAuth2AuthorizedClientService authorizedClientService( ClientRegistrationRepository clientRegistrationRepository) { return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository); } @Bean public OAuth2AuthorizedClientRepository authorizedClientRepository( OAuth2AuthorizedClientService authorizedClientService) { return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService); } private ClientRegistration googleClientRegistration() { return CommonOAuth2Provider.GOOGLE.getBuilder("google") .clientId("google-client-id") .clientSecret("google-client-secret") .build(); } }
HttpSecurity.oauth2Login()
は、OAuth 2.0 ログインをカスタマイズするための多くの構成オプションを提供します。主な構成オプションは、対応するプロトコルエンドポイントにグループ化されます。
例: oauth2Login().authorizationEndpoint()
では 認証エンドポイントを構成できますが、 oauth2Login().tokenEndpoint()
では トークンエンドポイントを構成できます。
次のコードは例を示しています。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2Login -> oauth2Login .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint ... ) .redirectionEndpoint(redirectionEndpoint -> redirectionEndpoint ... ) .tokenEndpoint(tokenEndpoint -> tokenEndpoint ... ) .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint ... ) ); } }
oauth2Login()
DSL の主なゴールは、仕様で定義されているように、命名に厳密に整合することでした。
OAuth 2.0 認可フレームワークは、プロトコルエンドポイント (英語) を次のように定義します。
認可プロセスでは、2 つの認可サーバーエンドポイント(HTTP リソース)を使用します。
1 つのクライアントエンドポイントと同様に:
OpenID Connect Core 1.0 仕様では、UserInfo エンドポイント (英語) を次のように定義しています。
UserInfo エンドポイントは、認証されたエンドユーザーに関するクレームを返す OAuth 2.0 保護リソースです。エンドユーザーに関するリクエストされたクレームを取得するために、クライアントは OpenID Connect 認証を通じて取得されたアクセストークンを使用して UserInfo エンドポイントにリクエストを行います。通常、これらのクレームは、クレームの名前と値のペアのコレクションを含む JSON オブジェクトによって表されます。
次のコードは、 oauth2Login()
DSL で使用可能な完全な構成オプションを示しています。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2Login -> oauth2Login .clientRegistrationRepository(this.clientRegistrationRepository()) .authorizedClientRepository(this.authorizedClientRepository()) .authorizedClientService(this.authorizedClientService()) .loginPage("/login") .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint .baseUri(this.authorizationRequestBaseUri()) .authorizationRequestRepository(this.authorizationRequestRepository()) .authorizationRequestResolver(this.authorizationRequestResolver()) ) .redirectionEndpoint(redirectionEndpoint -> redirectionEndpoint .baseUri(this.authorizationResponseBaseUri()) ) .tokenEndpoint(tokenEndpoint -> tokenEndpoint .accessTokenResponseClient(this.accessTokenResponseClient()) ) .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint .userAuthoritiesMapper(this.userAuthoritiesMapper()) .userService(this.oauth2UserService()) .oidcUserService(this.oidcUserService()) .customUserType(GitHubOAuth2User.class, "github") ) ); } }
以下のセクションでは、使用可能な各構成オプションについて詳しく説明します。
デフォルトでは、OAuth 2.0 ログインページは DefaultLoginPageGeneratingFilter
によって自動生成されます。デフォルトのログインページには、設定された各 OAuth クライアントとその ClientRegistration.clientName
がリンクとして表示され、認可リクエスト(または OAuth 2.0 ログイン)を開始できます。
![]() | メモ |
---|---|
|
各 OAuth クライアントのリンクの宛先は、デフォルトで次のようになります。
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI
+ "/{registrationId}"
次の行に例を示します。
<a href="/oauth2/authorization/google">Google</a>
デフォルトのログインページをオーバーライドするには、 oauth2Login().loginPage()
および(オプションで) oauth2Login().authorizationEndpoint().baseUri()
を構成します。
次のリストに例を示します。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2Login -> oauth2Login .loginPage("/login/oauth2") ... .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint .baseUri("/login/oauth2/authorization") ... ) ); } }
![]() | 重要 |
---|---|
カスタムログインページを表示できる |
![]() | ヒント |
---|---|
前述のように、 次の行に例を示します。 <a href="/login/oauth2/authorization/google">Google</a> |
リダイレクトエンドポイントは、認可サーバーがリソース所有者ユーザーエージェントを介してクライアントに認可レスポンス(認可資格情報を含む)を返すために使用されます。
![]() | ヒント |
---|---|
OAuth 2.0 Login は認証コード付与を活用します。認証情報は認証コードです。 |
デフォルトの Authorization Response baseUri
(リダイレクトエンドポイント)は /login/oauth2/code/*
であり、 OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI
で定義されています。
Authorization Response baseUri
をカスタマイズする場合は、次の例に示すように構成します。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2Login -> oauth2Login .redirectionEndpoint(redirectionEndpoint -> redirectionEndpoint .baseUri("/login/oauth2/callback/*") ... ) ); } }
![]() | 重要 |
---|---|
また、 次のリストに例を示します。 return CommonOAuth2Provider.GOOGLE.getBuilder("google") .clientId("google-client-id") .clientSecret("google-client-secret") .redirectUriTemplate("{baseUrl}/login/oauth2/callback/{registrationId}") .build(); |
UserInfo エンドポイントには、次のサブセクションで説明するように、いくつかの構成オプションが含まれています。
ユーザーが OAuth 2.0 プロバイダーで正常に認証された後、 OAuth2User.getAuthorities()
(または OidcUser.getAuthorities()
)が新しいセットの GrantedAuthority
インスタンスにマッピングされ、認証の補完時に OAuth2AuthenticationToken
に提供されます。
![]() | ヒント |
---|---|
|
ユーザー権限をマッピングするときに選択できるオプションがいくつかあります。
GrantedAuthoritiesMapper
の実装を提供し、次の例に示すように構成します。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2Login -> oauth2Login .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint .userAuthoritiesMapper(this.userAuthoritiesMapper()) ... ) ); } private GrantedAuthoritiesMapper userAuthoritiesMapper() { return (authorities) -> { Set<GrantedAuthority> mappedAuthorities = new HashSet<>(); authorities.forEach(authority -> { if (OidcUserAuthority.class.isInstance(authority)) { OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority; OidcIdToken idToken = oidcUserAuthority.getIdToken(); OidcUserInfo userInfo = oidcUserAuthority.getUserInfo(); // Map the claims found in idToken and/or userInfo // to one or more GrantedAuthority's and add it to mappedAuthorities } else if (OAuth2UserAuthority.class.isInstance(authority)) { OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority; Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes(); // Map the attributes found in userAttributes // to one or more GrantedAuthority's and add it to mappedAuthorities } }); return mappedAuthorities; }; } }
または、次の例に示すように、 GrantedAuthoritiesMapper
@Bean
を登録して、構成に自動的に適用することもできます。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(withDefaults()); } @Bean public GrantedAuthoritiesMapper userAuthoritiesMapper() { ... } }
この戦略は GrantedAuthoritiesMapper
を使用するよりも高度ですが、 OAuth2UserRequest
および OAuth2User
(OAuth 2.0 UserService を使用する場合)または OidcUserRequest
および OidcUser
(OpenID Connect 1.0 UserService を使用する場合)にアクセスできるため、より柔軟です。
OAuth2UserRequest
(および OidcUserRequest
)は、関連する OAuth2AccessToken
へのアクセスを提供します。これは、ユーザーのカスタム権限をマップする前に、 委譲者が保護リソースから権限情報をフェッチする必要がある場合に非常に便利です。
次の例は、OpenID Connect 1.0 UserService を使用して、委譲ベースの戦略を実装および構成する方法を示しています。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2Login -> oauth2Login .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint .oidcUserService(this.oidcUserService()) ... ) ); } private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() { final OidcUserService delegate = new OidcUserService(); return (userRequest) -> { // Delegate to the default implementation for loading a user OidcUser oidcUser = delegate.loadUser(userRequest); OAuth2AccessToken accessToken = userRequest.getAccessToken(); Set<GrantedAuthority> mappedAuthorities = new HashSet<>(); // TODO // 1) Fetch the authority information from the protected resource using accessToken // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities // 3) Create a copy of oidcUser but use the mappedAuthorities instead oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo()); return oidcUser; }; } }
CustomUserTypesOAuth2UserService
は、カスタム OAuth2User
タイプのサポートを提供する OAuth2UserService
の実装です。
デフォルトの実装( DefaultOAuth2User
)がニーズに合わない場合は、 OAuth2User
の独自の実装を定義できます。
次のコードは、GitHub のカスタム OAuth2User
タイプを登録する方法を示しています。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2Login -> oauth2Login .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint .customUserType(GitHubOAuth2User.class, "github") ... ) ); } }
次のコードは、GitHub のカスタム OAuth2User
タイプの例を示しています。
public class GitHubOAuth2User implements OAuth2User { private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER"); private Map<String, Object> attributes; private String id; private String name; private String login; private String email; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } @Override public Map<String, Object> getAttributes() { if (this.attributes == null) { this.attributes = new HashMap<>(); this.attributes.put("id", this.getId()); this.attributes.put("name", this.getName()); this.attributes.put("login", this.getLogin()); this.attributes.put("email", this.getEmail()); } return attributes; } public String getId() { return this.id; } public void setId(String id) { this.id = id; } @Override public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getLogin() { return this.login; } public void setLogin(String login) { this.login = login; } public String getEmail() { return this.email; } public void setEmail(String email) { this.email = email; } }
![]() | ヒント |
---|---|
|
DefaultOAuth2UserService
は、標準の OAuth 2.0 プロバイダーをサポートする OAuth2UserService
の実装です。
![]() | メモ |
---|---|
|
DefaultOAuth2UserService
は、UserInfo エンドポイントでユーザー属性をリクエストするときに RestOperations
を使用します。
UserInfo リクエストの前処理をカスタマイズする必要がある場合は、 DefaultOAuth2UserService.setRequestEntityConverter()
にカスタム Converter<OAuth2UserRequest, RequestEntity<?>>
を提供できます。デフォルトの実装 OAuth2UserRequestEntityConverter
は、デフォルトで Authorization
ヘッダーに OAuth2AccessToken
を設定する UserInfo リクエストの RequestEntity
表現を構築します。
一方、UserInfo レスポンスのリアクティブ処理をカスタマイズする必要がある場合は、 DefaultOAuth2UserService.setRestOperations()
にカスタム構成の RestOperations
を提供する必要があります。デフォルトの RestOperations
は次のように構成されています。
RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
OAuth2ErrorResponseErrorHandler
は、OAuth 2.0 エラー(400 間違ったリクエスト)を処理できる ResponseErrorHandler
です。OAuth 2.0 Error パラメーターを OAuth2Error
に変換するために OAuth2ErrorHttpMessageConverter
を使用します。
DefaultOAuth2UserService
をカスタマイズする場合でも、 OAuth2UserService
の独自の実装を提供する場合でも、次の例に示すように構成する必要があります。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2Login -> oauth2Login .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint .userService(this.oauth2UserService()) ... ) ); } private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() { ... } }
OidcUserService
は、OpenID Connect 1.0 プロバイダーをサポートする OAuth2UserService
の実装です。
OidcUserService
は、UserInfo エンドポイントでユーザー属性をリクエストするときに DefaultOAuth2UserService
を活用します。
UserInfo リクエストの前処理および / または UserInfo レスポンスのリアクティブ処理をカスタマイズする必要がある場合、 OidcUserService.setOauth2UserService()
にカスタム構成の DefaultOAuth2UserService
を提供する必要があります。
OidcUserService
をカスタマイズするか、OpenID Connect 1.0 プロバイダーの OAuth2UserService
の独自の実装を提供するかどうかにかかわらず、次の例に示すように構成する必要があります。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(oauth2Login -> oauth2Login .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint .oidcUserService(this.oidcUserService()) ... ) ); } private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() { ... } }
OpenID Connect 1.0 認証は ID トークン (英語) を導入します。ID トークン (英語) は、クライアントが使用する場合の認可サーバーによるエンドユーザーの認証に関するクレームを含むセキュリティトークンです。
ID トークンは JSON Web トークン (英語) (JWT)として表され、JSON Web 署名 (英語) (JWS)を使用して署名する必要があります。
OidcIdTokenDecoderFactory
は、 OidcIdToken
署名検証に使用される JwtDecoder
を提供します。デフォルトのアルゴリズムは RS256
ですが、クライアントの登録時に割り当てられる場合は異なる場合があります。これらの場合、特定のクライアントに割り当てられた予想される JWS アルゴリズムを返すようにリゾルバーを構成できます。
JWS アルゴリズムリゾルバーは、 ClientRegistration
を受け入れ、クライアントに期待される JwsAlgorithm
を返す Function
です。 SignatureAlgorithm.RS256
または MacAlgorithm.HS256
次のコードは、すべての ClientRegistration
に対してデフォルトで MacAlgorithm.HS256
になるように OidcIdTokenDecoderFactory
@Bean
を構成する方法を示しています。
@Bean public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() { OidcIdTokenDecoderFactory idTokenDecoderFactory = new OidcIdTokenDecoderFactory(); idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> MacAlgorithm.HS256); return idTokenDecoderFactory; }
![]() | メモ |
---|---|
|
![]() | ヒント |
---|---|
OpenID Connect 1.0 認証用に複数の |
OpenID Connect セッション管理 1.0 では、クライアントを使用してプロバイダーのエンドユーザーをログアウトできます。利用可能な戦略の 1 つは RP からのログアウト (英語) です。
OpenID プロバイダーがセッション管理とディスカバリ (英語) の両方をサポートしている場合、クライアントは OpenID プロバイダーのディスカバリメタデータ (英語) から end_session_endpoint
URL
を取得できます。これは、次の例のように、 issuer-uri
を使用して ClientRegistration
を構成することで実現できます。
spring: security: oauth2: client: registration: okta: client-id: okta-client-id client-secret: okta-client-secret ... provider: okta: issuer-uri: https://dev-1234.oktapreview.com
…および RP 開始ログアウトを実装する OidcClientInitiatedLogoutSuccessHandler
は、次のように構成できます。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private ClientRegistrationRepository clientRegistrationRepository; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests .anyRequest().authenticated() ) .oauth2Login(withDefaults()) .logout(logout -> logout .logoutSuccessHandler(oidcLogoutSuccessHandler()) ); } private LogoutSuccessHandler oidcLogoutSuccessHandler() { OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository); // Sets the `URI` that the End-User's User Agent will be redirected to // after the logout has been performed at the Provider oidcLogoutSuccessHandler.setPostLogoutRedirectUri(URI.create("https://localhost:8080")); return oidcLogoutSuccessHandler; } }
OAuth 2.0 クライアント機能は、OAuth 2.0 認証フレームワーク (英語) で定義されているクライアントロールのサポートを提供します。
大まかに言うと、利用可能なコア機能は次のとおりです。
HTTP クライアントのサポート
WebClient
統合 (保護されたリソースをリクエストするため) HttpSecurity.oauth2Client()
DSL は、OAuth 2.0 クライアントが使用するコアコンポーネントをカスタマイズするための多くの設定オプションを提供します。さらに、 HttpSecurity.oauth2Client().authorizationCodeGrant()
では、認可コードの付与をカスタマイズできます。
次のコードは、 HttpSecurity.oauth2Client()
DSL によって提供される完全な構成オプションを示しています。
@EnableWebSecurity public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Client(oauth2Client -> oauth2Client .clientRegistrationRepository(this.clientRegistrationRepository()) .authorizedClientRepository(this.authorizedClientRepository()) .authorizedClientService(this.authorizedClientService()) .authorizationCodeGrant(authorizationCodeGrant -> authorizationCodeGrant .authorizationRequestRepository(this.authorizationRequestRepository()) .authorizationRequestResolver(this.authorizationRequestResolver()) .accessTokenResponseClient(this.accessTokenResponseClient()) ) ); } }
OAuth2AuthorizedClientManager
は、1 つ以上の OAuth2AuthorizedClientProvider
と協力して、OAuth 2.0 クライアントの認可(または再認可)を管理します。
次のコードは、 OAuth2AuthorizedClientManager
@Bean
を登録し、それを authorization_code
、 refresh_token
、 client_credentials
、 password
認可認可タイプのサポートを提供する OAuth2AuthorizedClientProvider
コンポジットに関連付ける方法の例を示しています。
@Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() .refreshToken() .clientCredentials() .password() .build(); DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; }
以下のセクションでは、OAuth 2.0 クライアントで使用されるコアコンポーネントと利用可能な設定オプションについて詳しく説明します。
ClientRegistration
は、OAuth 2.0 または OpenID Connect 1.0 プロバイダーに登録されたクライアントの表現です。
クライアント登録には、クライアント ID、クライアントシークレット、認可付与タイプ、リダイレクト URI、スコープ、認可 URI、トークン URI、その他の詳細などの情報が保持されます。
ClientRegistration
とそのプロパティは次のように定義されています。
public final class ClientRegistration { private String registrationId;private String clientId;
private String clientSecret;
private ClientAuthenticationMethod clientAuthenticationMethod;
private AuthorizationGrantType authorizationGrantType;
private String redirectUriTemplate;
private Set<String> scopes;
private ProviderDetails providerDetails; private String clientName;
public class ProviderDetails { private String authorizationUri;
private String tokenUri;
private UserInfoEndpoint userInfoEndpoint; private String jwkSetUri;
private Map<String, Object> configurationMetadata;
public class UserInfoEndpoint { private String uri;
private AuthenticationMethod authenticationMethod;
private String userNameAttributeName;
} } }
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
ClientRegistration
は、OpenID Connect プロバイダーの構成エンドポイント (英語) または認可サーバーのメタデータエンドポイント (英語) の検出を使用して最初に構成できます。
ClientRegistrations
は、次の例に見られるように、このメソッドで ClientRegistration
を構成するための便利なメソッドを提供します。
ClientRegistration clientRegistration =
ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build();
上記のコードは、シリーズ https://idp.example.com/issuer/.well-known/openid-configuration (英語)
、次に https://idp.example.com/.well-known/openid-configuration/issuer (英語)
、最後に https://idp.example.com/.well-known/oauth-authorization-server/issuer (英語)
で照会し、最初に停止して 200 レスポンスを返します。
別の方法として、 ClientRegistrations.fromOidcIssuerLocation()
を使用して、OpenID Connect プロバイダーの構成エンドポイントのみを照会できます。
ClientRegistrationRepository
は、OAuth 2.0/OpenID Connect 1.0 ClientRegistration
(s)のリポジトリとして機能します。
![]() | メモ |
---|---|
クライアント登録情報は最終的に保存され、関連する認可サーバーによって所有されます。このリポジトリは、認可サーバーに保存されているプライマリクライアント登録情報のサブセットを取得する機能を提供します。 |
Spring Boot 2.x 自動構成は、 spring.security.oauth2.client.registration. [registrationId]
の各プロパティを ClientRegistration
のインスタンスにバインドし、 ClientRegistrationRepository
内の各 ClientRegistration
インスタンスを構成します。
![]() | メモ |
---|---|
|
また、自動構成は、 ClientRegistrationRepository
を ApplicationContext
の @Bean
として登録し、アプリケーションで必要な場合に依存関係の注入に使用できるようにします。
次のリストに例を示します。
@Controller public class OAuth2ClientController { @Autowired private ClientRegistrationRepository clientRegistrationRepository; @GetMapping("/") public String index() { ClientRegistration oktaRegistration = this.clientRegistrationRepository.findByRegistrationId("okta"); ... return "index"; } }
OAuth2AuthorizedClient
は、認可クライアントの表現です。エンドユーザー(リソース所有者)がクライアントに保護されたリソースにアクセスする認可を与えた場合、クライアントは認可されたと見なされます。
OAuth2AuthorizedClient
は、 OAuth2AccessToken
(およびオプションの OAuth2RefreshToken
)を、認可を認可した Principal
エンドユーザーである ClientRegistration
(クライアント)およびリソース所有者に関連付ける目的に役立ちます。
OAuth2AuthorizedClientRepository
は、Web リクエスト間で OAuth2AuthorizedClient
を保持するロールを果たします。一方、 OAuth2AuthorizedClientService
の主なロールは、アプリケーションレベルで OAuth2AuthorizedClient
を管理することです。
開発者の観点から、 OAuth2AuthorizedClientRepository
または OAuth2AuthorizedClientService
は、クライアントに関連付けられた OAuth2AccessToken
をルックアップする機能を提供し、保護されたリソースリクエストを開始するために使用できるようにします。
次のリストに例を示します。
@Controller public class OAuth2ClientController { @Autowired private OAuth2AuthorizedClientService authorizedClientService; @GetMapping("/") public String index(Authentication authentication) { OAuth2AuthorizedClient authorizedClient = this.authorizedClientService.loadAuthorizedClient("okta", authentication.getName()); OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); ... return "index"; } }
![]() | メモ |
---|---|
Spring Boot 2.x 自動構成は、 |
OAuth2AuthorizedClientManager
は、 OAuth2AuthorizedClient
の全体的な管理を担当します。
主な責任は次のとおりです。
OAuth2AuthorizedClientProvider
を使用して、OAuth 2.0 クライアントを認可(または再認可)します。 OAuth2AuthorizedClientService
または OAuth2AuthorizedClientRepository
を使用して、 OAuth2AuthorizedClient
の永続性を委譲します。 OAuth2AuthorizedClientProvider
は、OAuth 2.0 クライアントを認可(または再認可)するための戦略を実装します。実装は通常、認可付与タイプを実装します。 authorization_code
、 client_credentials
など。
OAuth2AuthorizedClientManager
のデフォルトの実装は DefaultOAuth2AuthorizedClientManager
です。これは、委譲ベースの複合を使用して複数の認可付与タイプをサポートする可能性のある OAuth2AuthorizedClientProvider
に関連付けられています。 OAuth2AuthorizedClientProviderBuilder
を使用して、委譲ベースのコンポジットを構成および構築できます。
次のコードは、 authorization_code
、 refresh_token
、 client_credentials
、 password
認可付与タイプのサポートを提供する OAuth2AuthorizedClientProvider
コンポジットを構成および構築する方法の例を示しています。
@Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() .refreshToken() .clientCredentials() .password() .build(); DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; }
DefaultOAuth2AuthorizedClientManager
は、タイプ Function<OAuth2AuthorizeRequest, Map<String, Object>>
の contextAttributesMapper
にも関連付けられます。 Function<OAuth2AuthorizeRequest, Map<String, Object>>
は、 OAuth2AuthorizeRequest
から OAuth2AuthorizationContext
に関連付けられる属性の Map
への属性のマッピングを担当します。これは、 OAuth2AuthorizedClientProvider
に必要な(サポートされている)属性を指定する必要がある場合に役立ちます。 PasswordOAuth2AuthorizedClientProvider
では、リソース所有者の username
および password
が OAuth2AuthorizationContext.getAttributes()
で使用可能である必要があります。
次のコードは、 contextAttributesMapper
の例を示しています。
@Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .password() .refreshToken() .build(); DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); // Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters, // map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()` authorizedClientManager.setContextAttributesMapper(contextAttributesMapper()); return authorizedClientManager; } private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper() { return authorizeRequest -> { Map<String, Object> contextAttributes = Collections.emptyMap(); HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName()); String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME); String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD); if (StringUtils.hasText(username) && StringUtils.hasText(password)) { contextAttributes = new HashMap<>(); // `PasswordOAuth2AuthorizedClientProvider` requires both attributes contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username); contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password); } return contextAttributes; }; }
DefaultOAuth2AuthorizedClientManager
は、 HttpServletRequest
のコンテキストで 以内にを使用するように設計されています。 HttpServletRequest
コンテキストの 外側を操作する場合は、代わりに AuthorizedClientServiceOAuth2AuthorizedClientManager
を使用してください。
サービスアプリケーションは、 AuthorizedClientServiceOAuth2AuthorizedClientManager
を使用する場合の一般的な使用例です。多くの場合、サービスアプリケーションはユーザーの操作なしでバックグラウンドで実行され、通常はユーザーアカウントではなくシステムレベルのアカウントで実行されます。 client_credentials
付与タイプで構成された OAuth 2.0 クライアントは、サービスアプリケーションのタイプと見なすことができます。
次のコードは、 client_credentials
付与タイプのサポートを提供する AuthorizedClientServiceOAuth2AuthorizedClientManager
を構成する方法の例を示しています。
@Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService) { OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .clientCredentials() .build(); AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientService); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; }
![]() | メモ |
---|---|
認証コード (英語) 付与の詳細については、OAuth 2.0 認可フレームワークを参照してください。 |
![]() | メモ |
---|---|
認証コードの付与については、認可リクエスト / レスポンス (英語) プロトコルフローを参照してください。 |
OAuth2AuthorizationRequestRedirectFilter
は OAuth2AuthorizationRequestResolver
を使用して OAuth2AuthorizationRequest
を解決し、エンドユーザーのユーザーエージェントを認可サーバーの認可エンドポイントにリダイレクトすることで認可コード付与フローを開始します。
OAuth2AuthorizationRequestResolver
の主なロールは、提供された Web リクエストから OAuth2AuthorizationRequest
を解決することです。デフォルト実装 DefaultOAuth2AuthorizationRequestResolver
は、 registrationId
を抽出し、それを使用して関連 ClientRegistration
の OAuth2AuthorizationRequest
を構築する(デフォルト)パス /oauth2/authorization/{registrationId}
で一致します。
OAuth 2.0 クライアント登録用の次の Spring Boot 2.x プロパティがあるとします。
spring: security: oauth2: client: registration: okta: client-id: okta-client-id client-secret: okta-client-secret authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/authorized/okta" scope: read, write provider: okta: authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
ベースパス /oauth2/authorization/okta
を使用したリクエストは、 OAuth2AuthorizationRequestRedirectFilter
による認可リクエストリダイレクトを開始し、最終的に認可コード認可フローを開始します。
![]() | メモ |
---|---|
|
OAuth 2.0 クライアントがパブリッククライアント (英語) の場合、OAuth 2.0 クライアントの登録を次のように構成します。
spring: security: oauth2: client: registration: okta: client-id: okta-client-id client-authentication-method: none authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/authorized/okta" ...
コード交換用の証明キー (英語) (PKCE)を使用したパブリッククライアントがサポートされています。クライアントが信頼されていない環境(ネイティブアプリケーションまたは Web ブラウザーベースのアプリケーションなど)で実行されているため、その資格情報の機密性を維持できない場合、次の条件に該当する場合、PKCE が自動的に使用されます。
client-secret
は省略されます (または空) client-authentication-method
は「なし」に設定されます ( ClientAuthenticationMethod.NONE
) DefaultOAuth2AuthorizationRequestResolver
は、 UriComponentsBuilder
を使用して redirect-uri
の URI
テンプレート変数もサポートします。
次の構成では、サポートされているすべての URI
テンプレート変数を使用します。
spring: security: oauth2: client: registration: okta: ... redirect-uri: "{baseScheme}://{baseHost}{basePort}{basePath}/authorized/{registrationId}" ...
![]() | メモ |
---|---|
|
URI
テンプレート変数を使用して redirect-uri
を構成することは、OAuth 2.0 クライアントがプロキシサーバーの背後で実行されている場合に特に役立ちます。これにより、 redirect-uri
を展開するときに X-Forwarded-*
ヘッダーが使用されます。
OAuth2AuthorizationRequestResolver
が実現できる主な使用例の 1 つは、OAuth 2.0 認可フレームワークで定義された標準パラメーターを超える追加パラメーターで認可リクエストをカスタマイズする機能です。
例: OpenID Connect は、OAuth 2.0 認証フレームワーク (英語) で定義された標準パラメーターから拡張された、認証コード Flow (英語) の追加の OAuth 2.0 リクエストパラメーターを定義します。これらの拡張パラメーターの 1 つは prompt
パラメーターです。
![]() | メモ |
---|---|
オプション。認可サーバーがエンドユーザーに再認証と同意を求めるかどうかを指定する、ASCII 文字列値のスペース区切りの大文字と小文字を区別したリスト。定義されている値は、なし、ログイン、同意、select_account です。 |
次の例は、リクエストパラメーター prompt=consent
を含めることにより、 oauth2Login()
の Authorization Request をカスタマイズする OAuth2AuthorizationRequestResolver
を実装する方法を示しています。
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private ClientRegistrationRepository clientRegistrationRepository; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests .anyRequest().authenticated() ) .oauth2Login(oauth2Login -> oauth2Login .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint .authorizationRequestResolver( new CustomAuthorizationRequestResolver( this.clientRegistrationRepository))) ); } } public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { private final OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver; public CustomAuthorizationRequestResolver( ClientRegistrationRepository clientRegistrationRepository) { this.defaultAuthorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver( clientRegistrationRepository, "/oauth2/authorization"); } @Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { OAuth2AuthorizationRequest authorizationRequest = this.defaultAuthorizationRequestResolver.resolve(request);
return authorizationRequest != null ?
customAuthorizationRequest(authorizationRequest) : null; } @Override public OAuth2AuthorizationRequest resolve( HttpServletRequest request, String clientRegistrationId) { OAuth2AuthorizationRequest authorizationRequest = this.defaultAuthorizationRequestResolver.resolve( request, clientRegistrationId);
return authorizationRequest != null ?
customAuthorizationRequest(authorizationRequest) : null; } private OAuth2AuthorizationRequest customAuthorizationRequest( OAuth2AuthorizationRequest authorizationRequest) { Map<String, Object> additionalParameters = new LinkedHashMap<>(authorizationRequest.getAdditionalParameters()); additionalParameters.put("prompt", "consent");
return OAuth2AuthorizationRequest.from(authorizationRequest)
.additionalParameters(additionalParameters)
.build(); } }
カスタム | |
| |
| |
既存の | |
さらなる変更のために | |
デフォルトの |
![]() | ヒント |
---|---|
|
追加のリクエストパラメーターが特定のプロバイダに対して常に同じである単純なユースケースでは、 authorization-uri
に直接追加できます。
例: リクエストパラメーター prompt
の値がプロバイダー okta
に対して常に consent
である場合、単に以下のように構成します。
spring: security: oauth2: client: provider: okta: authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize?prompt=consent
上記の例は、標準パラメーターの上にカスタムパラメーターを追加する一般的な使用例を示しています。または、要件がより高度な場合は、 OAuth2AuthorizationRequest.authorizationRequestUri
プロパティをオーバーライドするだけで、認可リクエスト URI の作成を完全に制御できます。
次の例は、前の例の customAuthorizationRequest()
メソッドのバリエーションを示し、代わりに OAuth2AuthorizationRequest.authorizationRequestUri
プロパティをオーバーライドします。
private OAuth2AuthorizationRequest customAuthorizationRequest( OAuth2AuthorizationRequest authorizationRequest) { String customAuthorizationRequestUri = UriComponentsBuilder .fromUriString(authorizationRequest.getAuthorizationRequestUri()) .queryParam("prompt", "consent") .build(true) .toUriString(); return OAuth2AuthorizationRequest.from(authorizationRequest) .authorizationRequestUri(customAuthorizationRequestUri) .build(); }
AuthorizationRequestRepository
は、認可リクエストが開始されてから認可レスポンスが受信されるまで(コールバック)、 OAuth2AuthorizationRequest
の永続化を担当します。
![]() | ヒント |
---|---|
|
AuthorizationRequestRepository
のデフォルトの実装は HttpSessionOAuth2AuthorizationRequestRepository
で、 HttpSession
に OAuth2AuthorizationRequest
を格納します。
AuthorizationRequestRepository
のカスタム実装がある場合、次の例に示すように構成できます。
@EnableWebSecurity public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Client(oauth2Client -> oauth2Client .authorizationCodeGrant(authorizationCodeGrant -> authorizationCodeGrant .authorizationRequestRepository(this.authorizationRequestRepository()) ... ) ); } }
![]() | メモ |
---|---|
認証コードの付与については、アクセストークンリクエスト / レスポンス (英語) プロトコルフローを参照してください。 |
認可コード付与の OAuth2AccessTokenResponseClient
のデフォルト実装は DefaultAuthorizationCodeTokenResponseClient
です。これは、認可サーバーのトークンエンドポイントでアクセストークンの認可コードを交換するために RestOperations
を使用します。
DefaultAuthorizationCodeTokenResponseClient
は、トークンリクエストの前処理やトークンレスポンスのリアクティブ処理をカスタマイズできるため、非常に柔軟です。
トークンリクエストの前処理をカスタマイズする必要がある場合は、 DefaultAuthorizationCodeTokenResponseClient.setRequestEntityConverter()
にカスタム Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>>
を提供できます。デフォルトの実装 OAuth2AuthorizationCodeGrantRequestEntityConverter
は、標準 OAuth 2.0 アクセストークンリクエスト (英語) の RequestEntity
表現を構築します。ただし、カスタム Converter
を提供すると、標準のトークンリクエストを継承し、カスタムパラメーターを追加できます。
![]() | 重要 |
---|---|
カスタム |
一方、トークンレスポンスのリアクティブ処理をカスタマイズする必要がある場合は、 DefaultAuthorizationCodeTokenResponseClient.setRestOperations()
にカスタム構成の RestOperations
を提供する必要があります。デフォルトの RestOperations
は次のように構成されています。
RestTemplate restTemplate = new RestTemplate(Arrays.asList( new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
![]() | ヒント |
---|---|
Spring MVC |
OAuth2AccessTokenResponseHttpMessageConverter
は、OAuth 2.0 アクセストークンレスポンス用の HttpMessageConverter
です。OAuth 2.0 アクセストークンレスポンスパラメーターを OAuth2AccessTokenResponse
に変換するために使用されるカスタム Converter<Map<String, String>, OAuth2AccessTokenResponse>
を OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter()
に提供できます。
OAuth2ErrorResponseErrorHandler
は、OAuth 2.0 エラーを処理できる ResponseErrorHandler
です。400 不正なリクエスト。OAuth 2.0 Error パラメーターを OAuth2Error
に変換するために OAuth2ErrorHttpMessageConverter
を使用します。
DefaultAuthorizationCodeTokenResponseClient
をカスタマイズする場合でも、 OAuth2AccessTokenResponseClient
の独自の実装を提供する場合でも、次の例に示すように構成する必要があります。
@EnableWebSecurity public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Client(oauth2Client -> oauth2Client .authorizationCodeGrant(authorizationCodeGrant -> authorizationCodeGrant .accessTokenResponseClient(this.accessTokenResponseClient()) ... ) ); } }
![]() | メモ |
---|---|
リフレッシュトークン (英語) の詳細については、OAuth 2.0 Authorization フレームワークを参照してください。 |
![]() | メモ |
---|---|
リフレッシュトークンの付与については、アクセストークンリクエスト / レスポンス (英語) プロトコルフローを参照してください。 |
リフレッシュトークン付与の OAuth2AccessTokenResponseClient
のデフォルト実装は DefaultRefreshTokenTokenResponseClient
です。これは、認可サーバーのトークンエンドポイントでアクセストークンをリフレッシュするときに RestOperations
を使用します。
DefaultRefreshTokenTokenResponseClient
は、トークンリクエストの前処理やトークンレスポンスのリアクティブ処理をカスタマイズできるため、非常に柔軟です。
トークンリクエストの前処理をカスタマイズする必要がある場合は、 DefaultRefreshTokenTokenResponseClient.setRequestEntityConverter()
にカスタム Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>>
を提供できます。デフォルトの実装 OAuth2RefreshTokenGrantRequestEntityConverter
は、標準 OAuth 2.0 アクセストークンリクエスト (英語) の RequestEntity
表現を構築します。ただし、カスタム Converter
を提供すると、標準のトークンリクエストを継承し、カスタムパラメーターを追加できます。
![]() | 重要 |
---|---|
カスタム |
一方、トークンレスポンスのリアクティブ処理をカスタマイズする必要がある場合は、 DefaultRefreshTokenTokenResponseClient.setRestOperations()
にカスタム構成の RestOperations
を提供する必要があります。デフォルトの RestOperations
は次のように構成されています。
RestTemplate restTemplate = new RestTemplate(Arrays.asList( new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
![]() | ヒント |
---|---|
Spring MVC |
OAuth2AccessTokenResponseHttpMessageConverter
は、OAuth 2.0 アクセストークンレスポンス用の HttpMessageConverter
です。OAuth 2.0 アクセストークンレスポンスパラメーターを OAuth2AccessTokenResponse
に変換するために使用されるカスタム Converter<Map<String, String>, OAuth2AccessTokenResponse>
を OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter()
に提供できます。
OAuth2ErrorResponseErrorHandler
は、OAuth 2.0 エラーを処理できる ResponseErrorHandler
です。400 不正なリクエスト。OAuth 2.0 Error パラメーターを OAuth2Error
に変換するために OAuth2ErrorHttpMessageConverter
を使用します。
DefaultRefreshTokenTokenResponseClient
をカスタマイズする場合でも、 OAuth2AccessTokenResponseClient
の独自の実装を提供する場合でも、次の例に示すように構成する必要があります。
// Customize
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient = ...
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken(configurer -> configurer.accessTokenResponseClient(refreshTokenTokenResponseClient))
.build();
...
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
![]() | メモ |
---|---|
|
OAuth2RefreshToken
は、 authorization_code
および password
付与タイプのアクセストークンレスポンスでオプションで返されます。 OAuth2AuthorizedClient.getRefreshToken()
が使用可能であり、 OAuth2AuthorizedClient.getAccessToken()
の有効期限が切れている場合、 RefreshTokenOAuth2AuthorizedClientProvider
によって自動的にリフレッシュされます。
![]() | メモ |
---|---|
クライアント資格情報 (英語) 付与の詳細については、OAuth 2.0 認可フレームワークを参照してください。 |
![]() | メモ |
---|---|
クライアント資格情報の付与については、アクセストークンリクエスト / レスポンス (英語) プロトコルフローを参照してください。 |
クライアント資格情報付与の OAuth2AccessTokenResponseClient
のデフォルト実装は DefaultClientCredentialsTokenResponseClient
です。これは、認可サーバーのトークンエンドポイントでアクセストークンをリクエストするときに RestOperations
を使用します。
DefaultClientCredentialsTokenResponseClient
は、トークンリクエストの前処理やトークンレスポンスのリアクティブ処理をカスタマイズできるため、非常に柔軟です。
トークンリクエストの前処理をカスタマイズする必要がある場合は、 DefaultClientCredentialsTokenResponseClient.setRequestEntityConverter()
にカスタム Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>>
を提供できます。デフォルトの実装 OAuth2ClientCredentialsGrantRequestEntityConverter
は、標準 OAuth 2.0 アクセストークンリクエスト (英語) の RequestEntity
表現を構築します。ただし、カスタム Converter
を提供すると、標準のトークンリクエストを継承し、カスタムパラメーターを追加できます。
![]() | 重要 |
---|---|
カスタム |
一方、トークンレスポンスのリアクティブ処理をカスタマイズする必要がある場合は、 DefaultClientCredentialsTokenResponseClient.setRestOperations()
にカスタム構成の RestOperations
を提供する必要があります。デフォルトの RestOperations
は次のように構成されています。
RestTemplate restTemplate = new RestTemplate(Arrays.asList( new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
![]() | ヒント |
---|---|
Spring MVC |
OAuth2AccessTokenResponseHttpMessageConverter
は、OAuth 2.0 アクセストークンレスポンス用の HttpMessageConverter
です。OAuth 2.0 アクセストークンレスポンスパラメーターを OAuth2AccessTokenResponse
に変換するために使用されるカスタム Converter<Map<String, String>, OAuth2AccessTokenResponse>
を OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter()
に提供できます。
OAuth2ErrorResponseErrorHandler
は、OAuth 2.0 エラーを処理できる ResponseErrorHandler
です。400 不正なリクエスト。OAuth 2.0 Error パラメーターを OAuth2Error
に変換するために OAuth2ErrorHttpMessageConverter
を使用します。
DefaultClientCredentialsTokenResponseClient
をカスタマイズする場合でも、 OAuth2AccessTokenResponseClient
の独自の実装を提供する場合でも、次の例に示すように構成する必要があります。
// Customize
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient = ...
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(configurer -> configurer.accessTokenResponseClient(clientCredentialsTokenResponseClient))
.build();
...
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
![]() | メモ |
---|---|
|
OAuth 2.0 クライアント登録用の次の Spring Boot 2.x プロパティがあるとします。
spring: security: oauth2: client: registration: okta: client-id: okta-client-id client-secret: okta-client-secret authorization-grant-type: client_credentials scope: read, write provider: okta: token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
…そして OAuth2AuthorizedClientManager
@Bean
:
@Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .clientCredentials() .build(); DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; }
OAuth2AccessToken
は次のようにして入手できます。
@Controller public class OAuth2ClientController { @Autowired private OAuth2AuthorizedClientManager authorizedClientManager; @GetMapping("/") public String index(Authentication authentication, HttpServletRequest servletRequest, HttpServletResponse servletResponse) { OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta") .principal(authentication) .attributes(attrs -> { attrs.put(HttpServletRequest.class.getName(), servletRequest); attrs.put(HttpServletResponse.class.getName(), servletResponse); }) .build(); OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); ... return "index"; } }
![]() | メモ |
---|---|
|
![]() | メモ |
---|---|
リソース所有者のパスワード資格証明 (英語) 付与の詳細については、OAuth 2.0 認可フレームワークを参照してください。 |
![]() | メモ |
---|---|
リソース所有者パスワード資格情報の付与については、アクセストークンリクエスト / レスポンス (英語) プロトコルフローを参照してください。 |
リソース所有者パスワード資格情報付与の OAuth2AccessTokenResponseClient
のデフォルト実装は DefaultPasswordTokenResponseClient
です。これは、認可サーバーのトークンエンドポイントでアクセストークンをリクエストするときに RestOperations
を使用します。
DefaultPasswordTokenResponseClient
は、トークンリクエストの前処理やトークンレスポンスのリアクティブ処理をカスタマイズできるため、非常に柔軟です。
トークンリクエストの前処理をカスタマイズする必要がある場合は、 DefaultPasswordTokenResponseClient.setRequestEntityConverter()
にカスタム Converter<OAuth2PasswordGrantRequest, RequestEntity<?>>
を提供できます。デフォルトの実装 OAuth2PasswordGrantRequestEntityConverter
は、標準 OAuth 2.0 アクセストークンリクエスト (英語) の RequestEntity
表現を構築します。ただし、カスタム Converter
を提供すると、標準のトークンリクエストを継承し、カスタムパラメーターを追加できます。
![]() | 重要 |
---|---|
カスタム |
一方、トークンレスポンスのリアクティブ処理をカスタマイズする必要がある場合は、 DefaultPasswordTokenResponseClient.setRestOperations()
にカスタム構成の RestOperations
を提供する必要があります。デフォルトの RestOperations
は次のように構成されています。
RestTemplate restTemplate = new RestTemplate(Arrays.asList( new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
![]() | ヒント |
---|---|
Spring MVC |
OAuth2AccessTokenResponseHttpMessageConverter
は、OAuth 2.0 アクセストークンレスポンス用の HttpMessageConverter
です。OAuth 2.0 アクセストークンレスポンスパラメーターを OAuth2AccessTokenResponse
に変換するために使用されるカスタム Converter<Map<String, String>, OAuth2AccessTokenResponse>
を OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter()
に提供できます。
OAuth2ErrorResponseErrorHandler
は、OAuth 2.0 エラーを処理できる ResponseErrorHandler
です。400 不正なリクエスト。OAuth 2.0 Error パラメーターを OAuth2Error
に変換するために OAuth2ErrorHttpMessageConverter
を使用します。
DefaultPasswordTokenResponseClient
をカスタマイズする場合でも、 OAuth2AccessTokenResponseClient
の独自の実装を提供する場合でも、次の例に示すように構成する必要があります。
// Customize
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ...
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.password(configurer -> configurer.accessTokenResponseClient(passwordTokenResponseClient))
.refreshToken()
.build();
...
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
![]() | メモ |
---|---|
|
OAuth 2.0 クライアント登録用の次の Spring Boot 2.x プロパティがあるとします。
spring: security: oauth2: client: registration: okta: client-id: okta-client-id client-secret: okta-client-secret authorization-grant-type: password scope: read, write provider: okta: token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
…そして OAuth2AuthorizedClientManager
@Bean
:
@Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .password() .refreshToken() .build(); DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); // Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters, // map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()` authorizedClientManager.setContextAttributesMapper(contextAttributesMapper()); return authorizedClientManager; } private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper() { return authorizeRequest -> { Map<String, Object> contextAttributes = Collections.emptyMap(); HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName()); String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME); String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD); if (StringUtils.hasText(username) && StringUtils.hasText(password)) { contextAttributes = new HashMap<>(); // `PasswordOAuth2AuthorizedClientProvider` requires both attributes contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username); contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password); } return contextAttributes; }; }
OAuth2AccessToken
は次のようにして入手できます。
@Controller public class OAuth2ClientController { @Autowired private OAuth2AuthorizedClientManager authorizedClientManager; @GetMapping("/") public String index(Authentication authentication, HttpServletRequest servletRequest, HttpServletResponse servletResponse) { OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta") .principal(authentication) .attributes(attrs -> { attrs.put(HttpServletRequest.class.getName(), servletRequest); attrs.put(HttpServletResponse.class.getName(), servletResponse); }) .build(); OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); ... return "index"; } }
![]() | メモ |
---|---|
|
@RegisteredOAuth2AuthorizedClient
アノテーションは、メソッドパラメーターをタイプ OAuth2AuthorizedClient
の引数値に解決する機能を提供します。これは、 OAuth2AuthorizedClientManager
または OAuth2AuthorizedClientService
を使用して OAuth2AuthorizedClient
にアクセスするのに比べて、便利な代替手段です。
@Controller public class OAuth2ClientController { @GetMapping("/") public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) { OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); ... return "index"; } }
@RegisteredOAuth2AuthorizedClient
アノテーションは OAuth2AuthorizedClientArgumentResolver
によって処理されます。 OAuth2AuthorizedClientArgumentResolver
は OAuth2AuthorizedClientManager を直接使用するため、その機能を継承します。
OAuth 2.0 クライアントサポートは、 ExchangeFilterFunction
を使用して WebClient
と統合します。
ServletOAuth2AuthorizedClientExchangeFilterFunction
は、 OAuth2AuthorizedClient
を使用し、関連する OAuth2AccessToken
をベアラートークンとして含めることにより、保護されたリソースをリクエストするためのシンプルなメカニズムを提供します。 OAuth2AuthorizedClientManager を直接使用するため、次の機能を継承します。
クライアントがまだ認可されていない場合、 OAuth2AccessToken
がリクエストされます。
authorization_code
- フローを開始するために認可リクエストリダイレクトをトリガーする client_credentials
- アクセストークンはトークンエンドポイントから直接取得されます password
- アクセストークンはトークンエンドポイントから直接取得されます OAuth2AccessToken
の有効期限が切れている場合、 OAuth2AuthorizedClientProvider
が認可を実行できる場合、リフレッシュ(またはリフレッシュ)されます。 次のコードは、OAuth 2.0 クライアントサポートを使用して WebClient
を構成する方法の例を示しています。
@Bean WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); return WebClient.builder() .apply(oauth2Client.oauth2Configuration()) .build(); }
ServletOAuth2AuthorizedClientExchangeFilterFunction
は、 ClientRequest.attributes()
(リクエスト属性)から OAuth2AuthorizedClient
を解決することにより、(リクエストに)使用するクライアントを決定します。
次のコードは、 OAuth2AuthorizedClient
をリクエスト属性として設定する方法を示しています。
@GetMapping("/") public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) { String resourceUri = ... String body = webClient .get() .uri(resourceUri) .attributes(oauth2AuthorizedClient(authorizedClient)).retrieve() .bodyToMono(String.class) .block(); ... return "index"; }
次のコードは、 ClientRegistration.getRegistrationId()
をリクエスト属性として設定する方法を示しています。
@GetMapping("/") public String index() { String resourceUri = ... String body = webClient .get() .uri(resourceUri) .attributes(clientRegistrationId("okta")).retrieve() .bodyToMono(String.class) .block(); ... return "index"; }
OAuth2AuthorizedClient
または ClientRegistration.getRegistrationId()
のいずれもリクエスト属性として提供されない場合、 ServletOAuth2AuthorizedClientExchangeFilterFunction
はその構成に応じて、使用する デフォルトのクライアントを決定できます。
setDefaultOAuth2AuthorizedClient(true)
が構成され、ユーザーが HttpSecurity.oauth2Login()
を使用して認証した場合、現在の OAuth2AuthenticationToken
に関連付けられた OAuth2AccessToken
が使用されます。
次のコードは、特定の構成を示しています。
@Bean WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); oauth2Client.setDefaultOAuth2AuthorizedClient(true); return WebClient.builder() .apply(oauth2Client.oauth2Configuration()) .build(); }
![]() | 警告 |
---|---|
すべての HTTP リクエストはアクセストークンを受け取るため、この機能には注意が必要です。 |
あるいは、 setDefaultClientRegistrationId("okta")
が有効な ClientRegistration
で構成されている場合、 OAuth2AuthorizedClient
に関連付けられた OAuth2AccessToken
が使用されます。
次のコードは、特定の構成を示しています。
@Bean WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); oauth2Client.setDefaultClientRegistrationId("okta"); return WebClient.builder() .apply(oauth2Client.oauth2Configuration()) .build(); }
![]() | 警告 |
---|---|
すべての HTTP リクエストはアクセストークンを受け取るため、この機能には注意が必要です。 |
Spring Security は、2 つの形式の OAuth 2.0 ベアラートークン (英語) を使用したエンドポイントの保護をサポートしています。
これは、アプリケーションが権限管理を認可サーバー (英語) (Okta や Ping Identity など)に委譲している場合に便利です。この認可サーバーは、リソースサーバーがリクエストを認可するために調べることができます。
![]() | メモ |
---|---|
JWT: GitHub (英語) と Opaque トークン: GitHub (英語) の両方の作業サンプルが Spring Security リポジトリ: GitHub (英語) で利用可能です。 |
ほとんどのリソースサーバーサポートは spring-security-oauth2-resource-server
に収集されます。ただし、JWT のデコードと検証のサポートは spring-security-oauth2-jose
にあります。つまり、JWT でエンコードされたベアラートークンをサポートする作業リソースサーバーを使用するには両方が必要です。
Spring Boot を使用する場合、アプリケーションをリソースサーバーとして構成するには、2 つの基本的な手順が必要です。最初に、必要な依存関係を含め、2 番目に認可サーバーの場所を示します。
Spring Boot アプリケーションで、使用する認可サーバーを指定するには、次のようにします。
spring: security: oauth2: resourceserver: jwt: issuer-uri: https://idp.example.com/issuer
ここで、 https://idp.example.com/issuer (英語)
は、認可サーバーが発行する JWT トークンの iss
クレームに含まれる値です。リソースサーバーは、このプロパティを使用して、さらに自己構成を行い、認可サーバーの公開キーを検出し、受信 JWT を検証します。
![]() | メモ |
---|---|
|
以上です!
このプロパティとこれらの依存関係を使用すると、Resource Server は自動的に JWT エンコードされたベアラートークンを検証するように自身を構成します。
これは、決定論的な起動プロセスを通じてこれを実現します。
jwks_url
プロパティのレスポンスを処理する jwks_url
を照会するための検証戦略を構成する https://idp.example.com (英語)
に対して各 JWT iss
クレームを検証する検証戦略を構成します。このプロセスの結果、リソースサーバーが正常に起動するには、認可サーバーが起動してリクエストを受信する必要があります。
![]() | メモ |
---|---|
リソースサーバーがクエリを実行したときに認可サーバーがダウンした場合(適切なタイムアウトが与えられた場合)、起動は失敗します。 |
アプリケーションが起動すると、Resource Server は Authorization: Bearer
ヘッダーを含むリクエストの処理を試みます。
GET / HTTP/1.1 Authorization: Bearer some-token-value # Resource Server will process this
このスキームが示されている限り、Resource Server は Bearer Token 仕様に従ってリクエストの処理を試みます。
整形式の JWT が与えられると、リソースサーバーは次のことを行います。
jwks_url
エンドポイントから取得され、JWT と照合される公開鍵に対して署名を検証する exp
および nbf
タイムスタンプと、JWT の iss
クレームを検証します。 SCOPE_
を持つ機関にマップします。 ![]() | メモ |
---|---|
認可サーバーが新しい鍵を使用できるようになると、Spring Security は JWT の検証に使用される鍵を自動的にローテーションします。 |
デフォルトでは、結果の Authentication#getPrincipal
は Spring Security Jwt
オブジェクトであり、 Authentication#getName
は JWT の sub
プロパティ(存在する場合)にマップします。
ここから、次へのジャンプを検討してください。
認可サーバーが構成エンドポイントをサポートしていない場合、またはリソースサーバーが認可サーバーから独立して起動できる必要がある場合は、 jwk-set-uri
も提供できます。
spring: security: oauth2: resourceserver: jwt: issuer-uri: https://idp.example.com jwk-set-uri: https://idp.example.com/.well-known/jwks.json
![]() | メモ |
---|---|
JWK Set uri は標準化されていませんが、通常は認可サーバーのドキュメントに記載されています |
リソースサーバーは起動時に認可サーバーに ping を実行しません。 issuer-uri
を引き続き指定して、Resource Server が受信 JWT で iss
クレームを検証するようにします。
![]() | メモ |
---|---|
このプロパティは、DSL で直接指定することもできます。 |
Spring Boot がリソースサーバーに代わって生成する 2 つの @Bean
があります。
1 つ目は、アプリをリソースサーバーとして構成する WebSecurityConfigurerAdapter
です。 spring-security-oauth2-jose
を含めると、この WebSecurityConfigurerAdapter
は次のようになります。
protected void configure(HttpSecurity http) { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) }
アプリケーションが WebSecurityConfigurerAdapter
Bean を公開しない場合、Spring Boot は上記のデフォルトを公開します。
これを置き換えることは、アプリケーション内で Bean を公開するのと同じくらい簡単です。
@EnableWebSecurity public class MyCustomSecurityConfiguration extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http .authorizeRequests() .mvcMatchers("/messages/**").hasAuthority("SCOPE_message:read") .anyRequest().authenticated() .and() .oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(myConverter()); } }
上記では、 /messages/
で始まる URL の message:read
のスコープが必要です。
oauth2ResourceServer
DSL のメソッドも自動構成をオーバーライドまたは置き換えます。
例: Spring Boot が 2 番目に作成する @Bean
は、 String
トークンを Jwt
の検証済みインスタンスにデコードする JwtDecoder
です。
@Bean public JwtDecoder jwtDecoder() { return JwtDecoders.fromIssuerLocation(issuerUri); }
![]() | メモ |
---|---|
|
アプリケーションが JwtDecoder
Bean を公開しない場合、Spring Boot は上記のデフォルトを公開します。
そして、その構成は jwkSetUri()
を使用してオーバーライドするか、 decoder()
を使用して置き換えることができます。
認可サーバーの JWK Set Uri は 、構成プロパティとして構成することも、DSL で提供することもできます。
@EnableWebSecurity public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2ResourceServer() .jwt() .jwkSetUri("https://idp.example.com/.well-known/jwks.json"); } }
jwkSetUri()
の使用は、構成プロパティよりも優先されます。
jwkSetUri()
よりも強力なのは decoder()
です。これは、 JwtDecoder
の Boot 自動構成を完全に置き換えます。
@EnableWebSecurity public class DirectlyConfiguredJwtDecoder extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2ResourceServer() .jwt() .decoder(myCustomDecoder()); } }
これは、 validation 、 mapping 、または request timeouts の ようなより詳細な構成が必要な場合に便利です。
デフォルトでは、 NimbusJwtDecoder
、リソースサーバーは、 RS256
を使用したトークンのみを信頼および検証します。
これは、Spring Boot、 NimbusJwtDecoder ビルダー、または JWK セットレスポンス からカスタマイズできます。
アルゴリズムを設定する最も簡単な方法は、プロパティとしてです:
spring: security: oauth2: resourceserver: jwt: jws-algorithm: RS512 jwk-set-uri: https://idp.example.org/.well-known/jwks.json
ただし、より強力にするには、 NimbusJwtDecoder
に同梱されているビルダーを使用できます。
@Bean JwtDecoder jwtDecoder() { return NimbusJwtDecoder.fromJwkSetUri(this.jwkSetUri) .jwsAlgorithm(RS512).build(); }
jwsAlgorithm
を複数回呼び出すと、 NimbusJwtDecoder
は次のように複数のアルゴリズムを信頼するように構成されます。
@Bean JwtDecoder jwtDecoder() { return NimbusJwtDecoder.fromJwkSetUri(this.jwkSetUri) .jwsAlgorithm(RS512).jwsAlgorithm(EC512).build(); }
または、 jwsAlgorithms
を呼び出すことができます。
@Bean JwtDecoder jwtDecoder() { return NimbusJwtDecoder.fromJwkSetUri(this.jwkSetUri) .jwsAlgorithms(algorithms -> { algorithms.add(RS512); algorithms.add(EC512); }).build(); }
Spring Security の JWT サポートは Nimbus に基づいているため、その優れた機能もすべて使用できます。
例: Nimbus には、JWK Set URI レスポンスに基づいてアルゴリズムのセットを選択する JWSKeySelector
実装があります。これを使用して、 NimbusJwtDecoder
を次のように生成できます。
@Bean public JwtDecoder jwtDecoder() { // makes a request to the JWK Set endpoint JWSKeySelector<SecurityContext> jwsKeySelector = JWSAlgorithmFamilyJWSKeySelector.fromJWKSetURL(this.jwkSetUrl); DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>(); jwtProcessor.setJWSKeySelector(jwsKeySelector); return new NimbusJwtDecoder(jwtProcessor); }
JWK Set エンドポイントでリソースサーバーをバッキングするよりも簡単なのは、RSA 公開キーをハードコードすることです。公開鍵は、Spring Boot またはビルダーを使用するを介して提供できます。
Spring Boot を介したキーの指定は非常に簡単です。キーの場所は次のように指定できます。
spring: security: oauth2: resourceserver: jwt: public-key-location: classpath:my-key.pub
または、より洗練されたルックアップを可能にするために、 RsaKeyConversionServicePostProcessor
を後処理できます。
@Bean BeanFactoryPostProcessor conversionServiceCustomizer() { return beanFactory -> beanFactory.getBean(RsaKeyConversionServicePostProcessor.class) .setResourceLoader(new CustomResourceLoader()); }
キーの場所を指定します。
key.location: hfds://my-key.pub
そして、値をオートワイヤーします。
@Value("${key.location}")
RSAPublicKey key;
単一の対称キーの使用も簡単です。次のように、 SecretKey
をロードして適切な NimbusJwtDecoder
ビルダーを使用するだけです。
@Bean public JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withSecretKey(this.key).build(); }
OAuth 2.0 認可サーバーから発行される JWT は通常、 scope
または scp
属性のいずれかを持ち、付与されたスコープ(または権限)を示します。例:
{ …, "scope" : "messages contacts"}
この場合、Resource Server はこれらのスコープを付与された権限のリストに強制し、各スコープの前に文字列「SCOPE_」を付けようとします。
つまり、エンドポイントまたはメソッドを JWT から派生したスコープで保護するには、対応する式に次のプレフィックスを含める必要があります。
@EnableWebSecurity public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http .authorizeRequests(authorizeRequests -> authorizeRequests .mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts") .mvcMatchers("/messages/**").hasAuthority("SCOPE_messages") .anyRequest().authenticated() ) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); } }
または、同様にメソッドセキュリティで:
@PreAuthorize("hasAuthority('SCOPE_messages')") public List<Message> getMessages(...) {}
ただし、このデフォルトでは不十分な状況がいくつかあります。例: 一部の認可サーバーは scope
属性を使用せず、独自のカスタム属性を持っています。または、リソースサーバーは、属性または属性の構成を内部化された機関に適合させる必要がある場合もあります。
このために、DSL は jwtAuthenticationConverter()
を公開します:
@EnableWebSecurity public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(grantedAuthoritiesExtractor()); } } Converter<Jwt, AbstractAuthenticationToken> grantedAuthoritiesExtractor() { JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter (new GrantedAuthoritiesExtractor()); return jwtAuthenticationConverter; }
Jwt
を Authentication
に変換する責任があります。その構成の一部として、 Jwt
から付与された権限の Collection
に移行する補助コンバーターを提供できます。
その最終的なコンバーターは、以下の GrantedAuthoritiesExtractor
のようなものです。
static class GrantedAuthoritiesExtractor implements Converter<Jwt, Collection<GrantedAuthority>> { public Collection<GrantedAuthority> convert(Jwt jwt) { Collection<String> authorities = (Collection<String>) jwt.getClaims().get("mycustomclaim"); return authorities.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } }
柔軟性を高めるため、DSL はコンバーターを Converter<Jwt, AbstractAuthenticationToken>
を実装するクラスに完全に置き換えることをサポートしています。
static class CustomAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> { public AbstractAuthenticationToken convert(Jwt jwt) { return new CustomAuthenticationToken(jwt); } }
リソースサーバーは、認可サーバーの発行者 URI を示す最小限の Spring Boot 構成を使用して、 iss
クレームと exp
および nbf
タイムスタンプクレームをデフォルトで検証します。
検証をカスタマイズする必要がある状況では、Resource Server には 2 つの標準バリデーターが付属しており、カスタム OAuth2TokenValidator
インスタンスも受け入れます。
通常、JWT には有効期間があり、ウィンドウの開始は nbf
クレームで示され、終了は exp
クレームで示されます。
ただし、すべてのサーバーでクロックドリフトが発生する可能性があります。これにより、あるサーバーではトークンが期限切れになり、別のサーバーでは期限切れになります。これにより、分散システムでコラボレーションサーバーの数が増えると、実装の胸焼けが発生する可能性があります。
リソースサーバーは JwtTimestampValidator
を使用してトークンの有効期間を検証し、 clockSkew
で構成して上記の問題を軽減できます。
@Bean JwtDecoder jwtDecoder() { NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromIssuerLocation(issuerUri); OAuth2TokenValidator<Jwt> withClockSkew = new DelegatingOAuth2TokenValidator<>( new JwtTimestampValidator(Duration.ofSeconds(60)), new IssuerValidator(issuerUri)); jwtDecoder.setJwtValidator(withClockSkew); return jwtDecoder; }
![]() | メモ |
---|---|
デフォルトでは、Resource Server は 30 秒のクロックスキューを設定します。 |
aud
クレームのチェックの追加は、 OAuth2TokenValidator
API を使用すると簡単です。
public class AudienceValidator implements OAuth2TokenValidator<Jwt> { OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null); public OAuth2TokenValidatorResult validate(Jwt jwt) { if (jwt.getAudience().contains("messaging")) { return OAuth2TokenValidatorResult.success(); } else { return OAuth2TokenValidatorResult.failure(error); } } }
次に、リソースサーバーに追加するには、 JwtDecoder
インスタンスを指定するだけです。
@Bean JwtDecoder jwtDecoder() { NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromIssuerLocation(issuerUri); OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(); OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri); OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator); jwtDecoder.setJwtValidator(withAudience); return jwtDecoder; }
Spring Security は、Nimbus (英語) ライブラリを使用して、JWT の構文解析と署名の検証を行います。Spring Security は、各フィールド値の Nimbus の解釈と、それぞれを Java 型に強制する方法の対象となります。
例: Nimbus は Java 7 と互換性があるため、タイムスタンプフィールドを表すために Instant
を使用しません。
また、別のライブラリを使用したり、JWT 処理に使用したりすることもできます。これにより、調整が必要な独自の強制決定を行うことができます。
または、非常に単純に、リソースサーバーはドメイン固有の理由で JWT にクレームを追加または削除したい場合があります。
これらの目的のために、Resource Server は MappedJwtClaimSetConverter
を使用した JWT クレームセットのマッピングをサポートしています。
デフォルトでは、 MappedJwtClaimSetConverter
はクレームを次のタイプに強制しようとします。
請求 | Java タイプ |
| |
| |
| |
| |
| |
| |
| |
MappedJwtClaimSetConverter.withDefaults
を使用して、個々の申し立ての変換戦略を構成できます。
@Bean JwtDecoder jwtDecoder() { NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build(); MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter .withDefaults(Collections.singletonMap("sub", this::lookupUserIdBySub)); jwtDecoder.setClaimSetConverter(converter); return jwtDecoder; }
これにより、 sub
のデフォルトクレームコンバーターがオーバーライドされることを除き、すべてのデフォルトが保持されます。
MappedJwtClaimSetConverter
は、たとえば既存のシステムに適応するために、カスタムクレームを追加するためにも使用できます。
MappedJwtClaimSetConverter.withDefaults(Collections.singletonMap("custom", custom -> "value"));
また、同じ API を使用して、クレームを削除することも簡単です。
MappedJwtClaimSetConverter.withDefaults(Collections.singletonMap("legacyclaim", legacy -> null));
一度に複数のクレームを参照したり、クレームの名前を変更したりするような、より洗練されたシナリオでは、Resource Server は Converter<Map<String, Object>, Map<String,Object>>
を実装するクラスを受け入れます。
public class UsernameSubClaimAdapter implements Converter<Map<String, Object>, Map<String, Object>> { private final MappedJwtClaimSetConverter delegate = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap()); public Map<String, Object> convert(Map<String, Object> claims) { Map<String, Object> convertedClaims = this.delegate.convert(claims); String username = (String) convertedClaims.get("user_name"); convertedClaims.put("sub", username); return convertedClaims; } }
そして、インスタンスは通常のように提供できます:
@Bean JwtDecoder jwtDecoder() { NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build(); jwtDecoder.setClaimSetConverter(new UsernameSubClaimAdapter()); return jwtDecoder; }
デフォルトでは、Resource Server は認可サーバーとの調整にそれぞれ 30 秒の接続およびソケットタイムアウトを使用します。
これはいくつかのシナリオでは短すぎるかもしれません。さらに、バックオフや発見などのより高度なパターンは考慮されません。
Resource Server が認可サーバーに接続する方法を調整するために、 NimbusJwtDecoder
は RestOperations
のインスタンスを受け入れます。
@Bean public JwtDecoder jwtDecoder(RestTemplateBuilder builder) { RestOperations rest = builder .setConnectionTimeout(60000) .setReadTimeout(60000) .build(); NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).restOperations(rest).build(); return jwtDecoder; }
通常、Opaque トークンは、認可サーバーによってホストされる OAuth 2.0 イントロスペクションエンドポイント (英語) を介して検証できます。これは、失効が必要な場合に便利です。
Spring Boot を使用する場合、イントロスペクションを使用するリソースサーバーとしてアプリケーションを構成するには、2 つの基本的な手順が必要です。まず、必要な依存関係を含め、次に、イントロスペクションエンドポイントの詳細を示します。
イントロスペクションエンドポイントの場所を指定するには、次のようにします。
security: oauth2: resourceserver: opaque-token: introspection-uri: https://idp.example.com/introspect client-id: client client-secret: secret
ここで、 https://idp.example.com/introspect (英語)
は認証サーバーによってホストされるイントロスペクションエンドポイントであり、 client-id
および client-secret
はそのエンドポイントをヒットするために必要な資格情報です。
リソースサーバーはこれらのプロパティを使用して、さらに自己構成し、受信 JWT を検証します。
![]() | メモ |
---|---|
イントロスペクションを使用する場合、認可サーバーの言葉は法律です。認可サーバーがトークンが有効であるとレスポンスした場合、有効です。 |
以上です!
このプロパティとこれらの依存関係が使用されると、リソースサーバーは自動的に不透明なベアラートークンを検証するように構成します。
この起動プロセスは、エンドポイントを検出する必要がなく、追加の検証ルールが追加されないため、JWT よりもかなり単純です。
アプリケーションが起動すると、Resource Server は Authorization: Bearer
ヘッダーを含むリクエストの処理を試みます。
GET / HTTP/1.1 Authorization: Bearer some-token-value # Resource Server will process this
このスキームが示されている限り、Resource Server は Bearer Token 仕様に従ってリクエストの処理を試みます。
Opaque トークンを指定すると、リソースサーバーは
{ 'active' : true }
属性のレスポンスをインスペクションする SCOPE_
を持つ機関にマップする 結果として得られる Authentication#getPrincipal
は、デフォルトでは Spring Security OAuth2AuthenticatedPrincipal (Javadoc)
オブジェクトであり、 Authentication#getName
はトークンの sub
プロパティ(存在する場合)にマップします。
ここから、次の場所にジャンプできます。
トークンが認証されると、 BearerTokenAuthentication
のインスタンスが SecurityContext
に設定されます。
つまり、構成で @EnableWebMvc
を使用する場合、 @Controller
メソッドで使用できます。
@GetMapping("/foo") public String foo(BearerTokenAuthentication authentication) { return authentication.getTokenAttributes().get("sub") + " is the subject"; }
BearerTokenAuthentication
は OAuth2AuthenticatedPrincipal
を保持するため、コントローラーメソッドでも使用できることも意味します。
@GetMapping("/foo") public String foo(@AuthenticationPrincipal OAuth2AuthenticatedPrincipal principal) { return principal.getAttribute("sub") + " is the subject"; }
Spring Boot がリソースサーバーに代わって生成する 2 つの @Bean
があります。
1 つ目は、アプリをリソースサーバーとして構成する WebSecurityConfigurerAdapter
です。Opaque トークンを使用する場合、この WebSecurityConfigurerAdapter
は次のようになります。
protected void configure(HttpSecurity http) { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken) }
アプリケーションが WebSecurityConfigurerAdapter
Bean を公開しない場合、Spring Boot は上記のデフォルトを公開します。
これを置き換えることは、アプリケーション内で Bean を公開するのと同じくらい簡単です。
@EnableWebSecurity public class MyCustomSecurityConfiguration extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http .authorizeRequests() .mvcMatchers("/messages/**").hasAuthority("SCOPE_message:read") .anyRequest().authenticated() .and() .oauth2ResourceServer() .opaqueToken() .introspector(myIntrospector()); } }
上記では、 /messages/
で始まる URL の message:read
のスコープが必要です。
oauth2ResourceServer
DSL のメソッドも自動構成をオーバーライドまたは置き換えます。
例: 2 番目の @Bean
Spring Boot が作成する OpaqueTokenIntrospector
は、 String
トークンを OAuth2AuthenticatedPrincipal
の検証済みインスタンスにデコードします。
@Bean public OpaqueTokenIntrospector introspector() { return new NimbusOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret); }
アプリケーションが OpaqueTokenIntrospector
Bean を公開しない場合、Spring Boot は上記のデフォルトを公開します。
そして、その構成は、 introspectionUri()
および introspectionClientCredentials()
を使用してオーバーライドするか、 introspector()
を使用して置き換えることができます。
認可サーバーの Introspection Uri は 、構成プロパティとして構成するか、DSL で提供できます。
@EnableWebSecurity public class DirectlyConfiguredIntrospectionUri extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2ResourceServer() .opaqueToken() .introspectionUri("https://idp.example.com/introspect") .introspectionClientCredentials("client", "secret"); } }
introspectionUri()
の使用は、構成プロパティよりも優先されます。
introspectionUri()
よりも強力なのは introspector()
です。これは、 OpaqueTokenIntrospector
の Boot 自動構成を完全に置き換えます。
@EnableWebSecurity public class DirectlyConfiguredIntrospector extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2ResourceServer() .opaqueToken() .introspector(myCustomIntrospector()); } }
これは、権限マッピング、JWT の取り消し、またはリクエストタイムアウトなどのより深い構成が必要な場合に便利です。
OAuth 2.0 Introspection エンドポイントは、通常、 scope
属性を返し、付与されたスコープ(または権限)を示します。例:
{ …, "scope" : "messages contacts"}
この場合、Resource Server はこれらのスコープを付与された権限のリストに強制し、各スコープの前に文字列「SCOPE_」を付けようとします。
つまり、Opaque トークンから派生したスコープを持つエンドポイントまたはメソッドを保護するには、対応する式に次のプレフィックスを含める必要があります。
@EnableWebSecurity public class MappedAuthorities extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) { http .authorizeRequests(authorizeRequests -> authorizeRequests .mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts") .mvcMatchers("/messages/**").hasAuthority("SCOPE_messages") .anyRequest().authenticated() ) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken); } }
または、同様にメソッドセキュリティで:
@PreAuthorize("hasAuthority('SCOPE_messages')") public List<Message> getMessages(...) {}
デフォルトでは、Opaque トークンサポートは、イントロスペクションレスポンスからスコープ要求を抽出し、個々の GrantedAuthority
インスタンスに解析します。
例: イントロスペクションのレスポンスが次の場合:
{ "active" : true, "scope" : "message:read message:write" }
次に、Resource Server は、 message:read
用と message:write
用の 2 つの権限を持つ Authentication
を生成します。
これはもちろん、属性セットを見て独自の方法で変換するカスタム OpaqueTokenIntrospector
を使用してカスタマイズできます。
public class CustomAuthoritiesOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private OpaqueTokenIntrospector delegate = new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret"); public OAuth2AuthenticatedPrincipal introspect(String token) { OAuth2AuthenticatedPrincipal principal = this.delegate.introspect(token); return new DefaultOAuth2AuthenticatedPrincipal( principal.getName(), principal.getAttributes(), extractAuthorities(principal)); } private Collection<GrantedAuthority> extractAuthorities(OAuth2AuthenticatedPrincipal principal) { List<String> scopes = principal.getAttribute(OAuth2IntrospectionClaimNames.SCOPE); return scopes.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } }
その後、このカスタムイントロスペクターは、 @Bean
として公開するだけで構成できます。
@Bean public OpaqueTokenIntrospector introspector() { return new CustomAuthoritiesOpaqueTokenIntrospector(); }
デフォルトでは、Resource Server は認可サーバーとの調整にそれぞれ 30 秒の接続およびソケットタイムアウトを使用します。
これはいくつかのシナリオでは短すぎるかもしれません。さらに、バックオフや発見などのより高度なパターンは考慮されません。
Resource Server が認可サーバーに接続する方法を調整するために、 NimbusOpaqueTokenIntrospector
は RestOperations
のインスタンスを受け入れます。
@Bean public OpaqueTokenIntrospector introspector(RestTemplateBuilder builder) { RestOperations rest = builder .basicAuthentication(clientId, clientSecret) .setConnectionTimeout(60000) .setReadTimeout(60000) .build(); return new NimbusOpaqueTokenIntrospector(introspectionUri, rest); }
よくある質問は、イントロスペクションが JWT と互換性があるかどうかです。Spring Security の Opaque トークンサポートは、トークンの形式を気にしないように設計されています。提供されたイントロスペクションエンドポイントにトークンを喜んで渡します。
JWT が取り消された場合に備えて、リクエストごとに認可サーバーで確認する必要がある要件があるとしましょう。
トークンに JWT 形式を使用している場合でも、検証方法はイントロスペクションです。つまり、次のようにします。
spring: security: oauth2: resourceserver: opaque-token: introspection-uri: https://idp.example.org/introspection client-id: client client-secret: secret
この場合、結果の Authentication
は BearerTokenAuthentication
になります。対応する OAuth2AuthenticatedPrincipal
の属性は、イントロスペクションエンドポイントによって返されたものです。
しかし、奇妙なことに、イントロスペクションエンドポイントは、トークンがアクティブであるかどうかのみを返します。それで?
この場合、エンドポイントにヒットするカスタム OpaqueTokenIntrospector
を作成できますが、返されたプリンシパルを更新して、属性として JWT クレームを取得します。
public class JwtOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private OpaqueTokenIntrospector delegate = new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret"); private JwtDecoder jwtDecoder = new NimbusJwtDecoder(new ParseOnlyJWTProcessor()); public OAuth2AuthenticatedPrincipal introspect(String token) { OAuth2AuthenticatedPrincipal principal = this.delegate.introspect(token); try { Jwt jwt = this.jwtDecoder.decode(token); return new DefaultOAuth2AuthenticatedPrincipal(jwt.getClaims(), NO_AUTHORITIES); } catch (JwtException e) { throw new OAuth2IntrospectionException(e); } } private static class ParseOnlyJWTProcessor extends DefaultJWTProcessor<SecurityContext> { JWTClaimsSet process(SignedJWT jwt, SecurityContext context) throws JOSEException { return jwt.getJWTClaimSet(); } } }
その後、このカスタムイントロスペクターは、 @Bean
として公開するだけで構成できます。
@Bean public OpaqueTokenIntrospector introspector() { return new JwtOpaqueTokenIntropsector(); }
一般的に、リソースサーバーは、基になるユーザーを気にするのではなく、付与された権限を気にします。
ただし、認証ステートメントをユーザーに結び付けることが重要な場合があります。
適切な ClientRegistrationRepository
をセットアップして、アプリケーションが spring-security-oauth2-client
も使用している場合、これはカスタム OpaqueTokenIntrospector
で非常に簡単です。以下のこの実装は、3 つのことを行います。
/userinfo
エンドポイントに関連付けられた適切なクライアント登録を検索する /userinfo
エンドポイントからレスポンスを呼び出して返する public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private final OpaqueTokenIntrospector delegate = new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret"); private final OAuth2UserService oauth2UserService = new DefaultOAuth2UserService(); private final ClientRegistrationRepository repository; // ... constructor @Override public OAuth2AuthenticatedPrincipal introspect(String token) { OAuth2AuthenticatedPrincipal authorized = this.delegate.introspect(token); Instant issuedAt = authorized.getAttribute(ISSUED_AT); Instant expiresAt = authorized.getAttribute(EXPIRES_AT); ClientRegistration clientRegistration = this.repository.findByRegistrationId("registration-id"); OAuth2AccessToken token = new OAuth2AccessToken(BEARER, token, issuedAt, expiresAt); OAuth2UserRequest oauth2UserRequest = new OAuth2UserRequest(clientRegistration, token); return this.oauth2UserService.loadUser(oauth2UserRequest); } }
spring-security-oauth2-client
を使用していない場合でも、非常に簡単です。 WebClient
の独自のインスタンスで /userinfo
を呼び出すだけです。
public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private final OpaqueTokenIntrospector delegate = new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret"); private final WebClient rest = WebClient.create(); @Override public OAuth2AuthenticatedPrincipal introspect(String token) { OAuth2AuthenticatedPrincipal authorized = this.delegate.introspect(token); return makeUserInfoRequest(authorized); } }
いずれにしても、 OpaqueTokenIntrospector
を作成したら、それを @Bean
として公開してデフォルトをオーバーライドする必要があります。
@Bean OpaqueTokenIntrospector introspector() { return new UserInfoOpaqueTokenIntrospector(...); }
場合によっては、両方の種類のトークンにアクセスする必要があります。例: 1 つのテナントが JWT を発行し、他のテナントが Opaque トークンを発行する複数のテナントをサポートできます。
リクエスト時にこの決定を行う必要がある場合は、 AuthenticationManagerResolver
を使用して次のように実現できます。
@Bean AuthenticationManagerResolver<HttpServletRequest> tokenAuthenticationManagerResolver() { BearerTokenResolver bearerToken = new DefaultBearerTokenResolver(); JwtAuthenticationProvider jwt = jwt(); OpaqueTokenAuthenticationProvider opaqueToken = opaqueToken(); return request -> { String token = bearerToken.resolve(request); if (isAJwt(token)) { return jwt::authenticate; } else { return opaqueToken::authenticate; } } }
そして、DSL でこの AuthenticationManagerResolver
を指定します。
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.authenticationManagerResolver(this.tokenAuthenticationManagerResolver);
リソースサーバーは、何らかのテナント ID をキーとするベアラートークンを検証するための複数の戦略がある場合、マルチテナントと見なされます。
例: リソースサーバーは、2 つの異なる認可サーバーからベアラートークンを受け入れる場合があります。または、認可サーバーが複数の発行者を表している場合があります。
いずれの場合も、実行する必要がある 2 つの事柄と、それらの選択方法に関連するトレードオフがあります。
リクエストマテリアルによるテナントの解決は、次のように、実行時に AuthenticationManager
を決定する AuthenticationManagerResolver
を実装することで実行できます。
@Component public class TenantAuthenticationManagerResolver implements AuthenticationManagerResolver<HttpServletRequest> { private final BearerTokenResolver resolver = new DefaultBearerTokenResolver(); private final TenantRepository tenants;private final Map<String, AuthenticationManager> authenticationManagers = new ConcurrentHashMap<>();
public TenantAuthenticationManagerResolver(TenantRepository tenants) { this.tenants = tenants; } @Override public AuthenticationManager resolve(HttpServletRequest request) { return this.authenticationManagers.computeIfAbsent(toTenant(request), this::fromTenant); } private String toTenant(HttpServletRequest request) { String[] pathParts = request.getRequestURI().split("/"); return pathParts.length > 0 ? pathParts[1] : null; } private AuthenticationManager fromTenant(String tenant) { return Optional.ofNullable(this.tenants.get(tenant))
.map(JwtDecoders::fromIssuerLocation)
.map(JwtAuthenticationProvider::new) .orElseThrow(() -> new IllegalArgumentException("unknown tenant"))::authenticate; } }
テナント情報の仮想ソース | |
テナント識別子をキーとする「AuthenticationManager」のキャッシュ | |
テナントの検索は、単に発行者の場所をその場で計算するよりも安全です - ルックアップはテナントのホワイトリストとして機能する | |
ディスカバリエンドポイントを介して |
そして、DSL でこの AuthenticationManagerResolver
を指定します。
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.authenticationManagerResolver(this.tenantAuthenticationManagerResolver);
クレームによるテナントの解決は、リクエスト資料による解決と同様です。唯一の本当の違いは、 toTenant
メソッドの実装です。
@Component public class TenantAuthenticationManagerResolver implements AuthenticationManagerResolver<HttpServletRequest> { private final BearerTokenResolver resolver = new DefaultBearerTokenResolver(); private final TenantRepository tenants;private final Map<String, AuthenticationManager> authenticationManagers = new ConcurrentHashMap<>();
public TenantAuthenticationManagerResolver(TenantRepository tenants) { this.tenants = tenants; } @Override public AuthenticationManager resolve(HttpServletRequest request) { return this.authenticationManagers.computeIfAbsent(toTenant(request), this::fromTenant);
} private String toTenant(HttpServletRequest request) { try { String token = this.resolver.resolve(request); return (String) JWTParser.parse(token).getJWTClaimsSet().getIssuer(); } catch (Exception e) { throw new IllegalArgumentException(e); } } private AuthenticationManager fromTenant(String tenant) { return Optional.ofNullable(this.tenants.get(tenant))
.map(JwtDecoders::fromIssuerLocation)
.map(JwtAuthenticationProvider::new) .orElseThrow(() -> new IllegalArgumentException("unknown tenant"))::authenticate; } }
テナント情報の仮想ソース | |
テナント識別子をキーとする「AuthenticationManager」のキャッシュ | |
テナントの検索は、単に発行者の場所をその場で計算するよりも安全です - ルックアップはテナントのホワイトリストとして機能する | |
ディスカバリエンドポイントを介して |
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.authenticationManagerResolver(this.tenantAuthenticationManagerResolver);
この戦略は単純ですが、JWT が AuthenticationManagerResolver
によって一度解析され、次に JwtDecoder
によって再び解析されるというトレードオフが伴うことに気づいたかもしれません。
Nimbus の JWTClaimSetAwareJWSKeySelector
を使用して JwtDecoder
を直接設定することにより、この余分な解析を軽減できます。
@Component public class TenantJWSKeySelector implements JWTClaimSetAwareJWSKeySelector<SecurityContext> { private final TenantRepository tenants;private final Map<String, JWSKeySelector<SecurityContext>> selectors = new ConcurrentHashMap<>();
public TenantJWSKeySelector(TenantRepository tenants) { this.tenants = tenants; } @Override public List<? extends Key> selectKeys(JWSHeader jwsHeader, JWTClaimsSet jwtClaimsSet, SecurityContext securityContext) throws KeySourceException { return this.selectors.computeIfAbsent(toTenant(jwtClaimsSet), this::fromTenant) .selectJWSKeys(jwsHeader, securityContext); } private String toTenant(JWTClaimsSet claimSet) { return (String) claimSet.getClaim("iss"); } private JWSKeySelector<SecurityContext> fromTenant(String tenant) { return Optional.ofNullable(this.tenantRepository.findById(tenant))
.map(t -> t.getAttrbute("jwks_uri")) .map(this::fromUri) .orElseThrow(() -> new IllegalArgumentException("unknown tenant")); } private JWSKeySelector<SecurityContext> fromUri(String uri) { try { return JWSAlgorithmFamilyJWSKeySelector.fromJWKSetURL(new URL(uri));
} catch (Exception e) { throw new IllegalArgumentException(e); } } }
テナント情報の仮想ソース | |
テナント識別子をキーとする「JWKKeySelector」のキャッシュ | |
テナントのルックアップは、単に JWK Set エンドポイントをオンザフライで計算するよりも安全です - ルックアップはテナントホワイトリストとして機能する | |
JWK Set エンドポイントから返されるキーの種類を介して |
上記のキーセレクターは、多くのキーセレクターの構成です。JWT の iss
クレームに基づいて、使用するキーセレクターを選択します。
![]() | メモ |
---|---|
このアプローチを使用するには、トークンの署名の一部としてクレームセットを含めるように認可サーバーが構成されていることを確認してください。これがなければ、発行者が悪役によって改ざんされていないという保証はありません。 |
次に、 JWTProcessor
を作成できます。
@Bean JWTProcessor jwtProcessor(JWTClaimSetJWSKeySelector keySelector) { ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor(); jwtProcessor.setJWTClaimSetJWSKeySelector(keySelector); return jwtProcessor; }
すでに見たように、テナント認識をこのレベルに下げるためのトレードオフは、より多くの構成です。もう少しあります。
次に、発行者を検証していることを確認します。ただし、発行者は JWT ごとに異なる可能性があるため、テナント対応バリデーターも必要になります。
@Component public class TenantJwtIssuerValidator implements OAuth2TokenValidator<Jwt> { private final TenantRepository tenants; private final Map<String, JwtIssuerValidator> validators = new ConcurrentHashMap<>(); public TenantJwtIssuerValidator(TenantRepository tenants) { this.tenants = tenants; } @Override public OAuth2TokenValidatorResult validate(Jwt token) { return this.validators.computeIfAbsent(toTenant(token), this::fromTenant) .validate(token); } private String toTenant(Jwt jwt) { return jwt.getIssuer(); } private JwtIssuerValidator fromTenant(String tenant) { return Optional.ofNullable(this.tenants.findById(tenant)) .map(t -> t.getAttribute("issuer")) .map(JwtIssuerValidator::new) .orElseThrow(() -> new IllegalArgumentException("unknown tenant")); } }
テナント対応プロセッサーとテナント対応バリデーターができたため、 JwtDecoder
の作成に進むことができます:
@Bean JwtDecoder jwtDecoder(JWTProcessor jwtProcessor, OAuth2TokenValidator<Jwt> jwtValidator) { NimbusJwtDecoder decoder = new NimbusJwtDecoder(processor); OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<> (JwtValidators.createDefault(), this.jwtValidator); decoder.setJwtValidator(validator); return decoder; }
テナントの解決についてお話ししました。
リクエストマテリアルによってテナントを解決することを選択した場合は、同じメソッドでダウンストリームリソースサーバーに対処する必要があります。例: サブドメインで解決する場合、同じサブドメインを使用してダウンストリームリソースサーバーをアドレス指定する必要があります。
ただし、ベアラートークンの要求によって解決する場合は、Spring Security のベアラートークン伝播のサポートについて学習してください。
デフォルトでは、Resource Server は Authorization
ヘッダーでベアラートークンを探します。ただし、これはいくつかの方法でカスタマイズできます。
例: カスタムヘッダーからベアラートークンを読み取る必要がある場合があります。これを実現するために、次の例に示すように、 HeaderBearerTokenResolver
インスタンスを DSL に接続できます。
http .oauth2ResourceServer() .bearerTokenResolver(new HeaderBearerTokenResolver("x-goog-iap-jwt-assertion"));
これでベアラートークンを所有しているため、それをダウンストリームサービスに渡すと便利かもしれません。 ServletBearerExchangeFilterFunction (Javadoc)
では、これは非常に簡単です。次の例で確認できます。
@Bean public WebClient rest() { return WebClient.builder() .filter(new ServletBearerExchangeFilterFunction()) .build(); }
上記の WebClient
を使用してリクエストを実行すると、Spring Security は現在の Authentication
を検索し、 AbstractOAuth2Token (Javadoc)
資格情報を抽出します。次に、 Authorization
ヘッダーでそのトークンを伝搬します。
例:
this.rest.get() .uri("https://other-service.example.com/endpoint") .retrieve() .bodyToMono(String.class) .block()
https://other-service.example.com/endpoint (英語)
を呼び出して、ベアラートークン Authorization
ヘッダーを追加します。
この動作をオーバーライドする必要がある場所では、次のようにヘッダーを自分で指定するだけです。
this.rest.get() .uri("https://other-service.example.com/endpoint") .headers(headers -> headers.setBearerAuth(overridingToken)) .retrieve() .bodyToMono(String.class) .block()
この場合、フィルターはフォールバックし、リクエストを Web フィルターチェーンの残りの部分に単純に転送します。
![]() | メモ |
---|---|
OAuth 2.0 クライアントフィルター機能 (Javadoc) とは異なり、このフィルター関数は、トークンが期限切れになった場合、トークンを更新しようとしません。このレベルのサポートを取得するには、OAuth 2.0 クライアントフィルターを使用してください。 |
現時点では RestTemplate
の専用サポートはありませんが、独自のインターセプターを使用して非常に簡単に伝播を実現できます。
@Bean RestTemplate rest() { RestTemplate rest = new RestTemplate(); rest.getInterceptors().add((request, body, execution) -> { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { return execution.execute(request, body); } if (!(authentication.getCredentials() instanceof AbstractOAuth2Token)) { return execution.execute(request, body); } AbstractOAuth2Token token = (AbstractOAuth2Token) authentication.getCredentials(); request.getHeaders().setBearerAuth(token.getTokenValue()); return execution.execute(request, body); }); return rest; }