高度な構成
OAuth 2.0 認可フレームワークは、プロトコルエンドポイント [IETF] (英語) を次のように定義します。
認可プロセスでは、2 つの認可サーバーエンドポイント(HTTP リソース)を使用します。
認可エンドポイント: クライアントがユーザーエージェントリダイレクトを介してリソース所有者から認可を取得するために使用します。
トークンエンドポイント: 通常はクライアント認証で、アクセストークンの認可付与を交換するためにクライアントによって使用されます。
1 つのクライアントエンドポイントと同様に:
リダイレクトエンドポイント: リソース所有者のユーザーエージェントを介してクライアントに認証資格情報を含むレスポンスを返すために認証サーバーによって使用されます。
OpenID Connect Core 1.0 仕様では、UserInfo エンドポイント (英語) を次のように定義しています。
UserInfo エンドポイントは、認証されたエンドユーザーに関するクレームを返す OAuth 2.0 保護リソースです。エンドユーザーに関するリクエストされたクレームを取得するために、クライアントは OpenID Connect 認証を通じて取得されたアクセストークンを使用して UserInfo エンドポイントにリクエストを行います。通常、これらのクレームは、クレームの名前と値のペアのコレクションを含む JSON オブジェクトによって表されます。
ServerHttpSecurity.oauth2Login()
は、OAuth 2.0 ログインをカスタマイズするためのいくつかの構成オプションを提供します。
次のコードは、oauth2Login()
DSL で使用可能な完全な構成オプションを示しています。
Java
Kotlin
@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.oauth2Login(oauth2 -> oauth2
.authenticationConverter(this.authenticationConverter())
.authenticationMatcher(this.authenticationMatcher())
.authenticationManager(this.authenticationManager())
.authenticationSuccessHandler(this.authenticationSuccessHandler())
.authenticationFailureHandler(this.authenticationFailureHandler())
.clientRegistrationRepository(this.clientRegistrationRepository())
.authorizedClientRepository(this.authorizedClientRepository())
.authorizedClientService(this.authorizedClientService())
.authorizationRequestResolver(this.authorizationRequestResolver())
.authorizationRequestRepository(this.authorizationRequestRepository())
.securityContextRepository(this.securityContextRepository())
);
return http.build();
}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
oauth2Login {
authenticationConverter = authenticationConverter()
authenticationMatcher = authenticationMatcher()
authenticationManager = authenticationManager()
authenticationSuccessHandler = authenticationSuccessHandler()
authenticationFailureHandler = authenticationFailureHandler()
clientRegistrationRepository = clientRegistrationRepository()
authorizedClientRepository = authorizedClientRepository()
authorizedClientService = authorizedClientService()
authorizationRequestResolver = authorizationRequestResolver()
authorizationRequestRepository = authorizationRequestRepository()
securityContextRepository = securityContextRepository()
}
}
return http.build()
}
}
以下のセクションでは、使用可能な各構成オプションについて詳しく説明します。
OAuth 2.0 ログインページ
デフォルトでは、OAuth 2.0 ログインページは LoginPageGeneratingWebFilter
によって自動生成されます。デフォルトのログインページには、設定された各 OAuth クライアントとその ClientRegistration.clientName
がリンクとして表示され、認可リクエスト(または OAuth 2.0 ログイン)を開始できます。
LoginPageGeneratingWebFilter が構成済みの OAuth クライアントのリンクを表示するには、登録された ReactiveClientRegistrationRepository が Iterable<ClientRegistration> も実装する必要があります。参考のために InMemoryReactiveClientRegistrationRepository を参照してください。 |
各 OAuth クライアントのリンクの宛先は、デフォルトで次のようになります。
"/oauth2/authorization/{registrationId}"
次の行に例を示します。
<a href="/oauth2/authorization/google">Google</a>
デフォルトのログインページを上書きするには、exceptionHandling().authenticationEntryPoint()
および(オプションで) oauth2Login().authorizationRequestResolver()
を構成します。
次のリストに例を示します。
Java
Kotlin
@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/login/oauth2"))
)
.oauth2Login(oauth2 -> oauth2
.authorizationRequestResolver(this.authorizationRequestResolver())
);
return http.build();
}
private ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver() {
ServerWebExchangeMatcher authorizationRequestMatcher =
new PathPatternParserServerWebExchangeMatcher(
"/login/oauth2/authorization/{registrationId}");
return new DefaultServerOAuth2AuthorizationRequestResolver(
this.clientRegistrationRepository(), authorizationRequestMatcher);
}
...
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
exceptionHandling {
authenticationEntryPoint = RedirectServerAuthenticationEntryPoint("/login/oauth2")
}
oauth2Login {
authorizationRequestResolver = authorizationRequestResolver()
}
}
return http.build()
}
private fun authorizationRequestResolver(): ServerOAuth2AuthorizationRequestResolver {
val authorizationRequestMatcher: ServerWebExchangeMatcher = PathPatternParserServerWebExchangeMatcher(
"/login/oauth2/authorization/{registrationId}"
)
return DefaultServerOAuth2AuthorizationRequestResolver(
clientRegistrationRepository(), authorizationRequestMatcher
)
}
...
}
カスタムログインページを表示できる @RequestMapping("/login/oauth2") を備えた @Controller を提供する必要があります。 |
前述のように、 次の行に例を示します。
|
リダイレクトエンドポイント
リダイレクトエンドポイントは、認可サーバーがリソース所有者ユーザーエージェントを介してクライアントに認可レスポンス(認可資格情報を含む)を返すために使用されます。
OAuth 2.0 Login は認証コード付与を活用します。認証情報は認証コードです。 |
デフォルトの認可レスポンスリダイレクトエンドポイントは /login/oauth2/code/{registrationId}
です。
Authorization Response リダイレクションエンドポイントをカスタマイズする場合は、次の例に示すように設定します。
Java
Kotlin
@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.oauth2Login(oauth2 -> oauth2
.authenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/login/oauth2/callback/{registrationId}"))
);
return http.build();
}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
oauth2Login {
authenticationMatcher = PathPatternParserServerWebExchangeMatcher("/login/oauth2/callback/{registrationId}")
}
}
return http.build()
}
}
また、 次のリストに例を示します。
|
UserInfo エンドポイント
UserInfo エンドポイントには、次のサブセクションで説明するように、いくつかの構成オプションが含まれています。
ユーザー権限のマッピング
ユーザーが OAuth 2.0 プロバイダーで正常に認証されると、OAuth2User.getAuthorities()
(または OidcUser.getAuthorities()
) には、OAuth2UserRequest.getAccessToken().getScopes()
から入力され、接頭辞 SCOPE_
が付けられた、許可された権限のリストが含まれます。これらの付与された権限は、認証の完了時に OAuth2AuthenticationToken
に提供される GrantedAuthority
インスタンスの新しいセットにマップされる場合があります。
OAuth2AuthenticationToken.getAuthorities() は、hasRole('USER') や hasRole('ADMIN') などでリクエストを認可するために使用されます。 |
ユーザー権限をマッピングするときに選択できるオプションがいくつかあります。
GrantedAuthoritiesMapper を使用する
GrantedAuthoritiesMapper
には、型 OAuth2UserAuthority
の特殊権限と権限ストリング OAUTH2_USER
(または OidcUserAuthority
と権限ストリング OIDC_USER
) を含む、許可された権限のリストが与えられます。
次の例に示すように、GrantedAuthoritiesMapper
@Bean
を登録して、構成に自動的に適用します。
Java
Kotlin
@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
...
.oauth2Login(withDefaults());
return http.build();
}
@Bean
public 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;
};
}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
oauth2Login { }
}
return http.build()
}
@Bean
fun userAuthoritiesMapper(): GrantedAuthoritiesMapper = GrantedAuthoritiesMapper { authorities: Collection<GrantedAuthority> ->
val mappedAuthorities = emptySet<GrantedAuthority>()
authorities.forEach { authority ->
if (authority is OidcUserAuthority) {
val idToken = authority.idToken
val userInfo = authority.userInfo
// Map the claims found in idToken and/or userInfo
// to one or more GrantedAuthority's and add it to mappedAuthorities
} else if (authority is OAuth2UserAuthority) {
val userAttributes = authority.attributes
// Map the attributes found in userAttributes
// to one or more GrantedAuthority's and add it to mappedAuthorities
}
}
mappedAuthorities
}
}
ReactiveOAuth2UserService を使用した委譲ベースの戦略
この戦略は GrantedAuthoritiesMapper
を使用するよりも高度ですが、OAuth2UserRequest
および OAuth2User
(OAuth 2.0 UserService を使用する場合)または OidcUserRequest
および OidcUser
(OpenID Connect 1.0 UserService を使用する場合)にアクセスできるため、より柔軟です。
OAuth2UserRequest
(および OidcUserRequest
)は、関連する OAuth2AccessToken
へのアクセスを提供します。これは、委譲者がユーザーのカスタム権限をマップする前に、保護されたリソースから権限情報を取得する必要がある場合に非常に便利です。
次の例は、OpenID Connect 1.0 UserService を使用して、委譲ベースの戦略を実装および構成する方法を示しています。
Java
Kotlin
@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
...
.oauth2Login(withDefaults());
return http.build();
}
@Bean
public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService();
return (userRequest) -> {
// Delegate to the default implementation for loading a user
return delegate.loadUser(userRequest)
.flatMap((oidcUser) -> {
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
ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName();
if (StringUtils.hasText(userNameAttributeName)) {
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo(), userNameAttributeName);
} else {
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
}
return Mono.just(oidcUser);
});
};
}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
oauth2Login { }
}
return http.build()
}
@Bean
fun oidcUserService(): ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
val delegate = OidcReactiveOAuth2UserService()
return ReactiveOAuth2UserService { userRequest ->
// Delegate to the default implementation for loading a user
delegate.loadUser(userRequest)
.flatMap { oidcUser ->
val accessToken = userRequest.accessToken
val mappedAuthorities = mutableSetOf<GrantedAuthority>()
// 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
val providerDetails = userRequest.getClientRegistration().getProviderDetails()
val userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName()
val mappedOidcUser = if (StringUtils.hasText(userNameAttributeName)) {
DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo, userNameAttributeName)
} else {
DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo)
}
Mono.just(mappedOidcUser)
}
}
}
}
OAuth 2.0 UserService
DefaultReactiveOAuth2UserService
は、標準の OAuth 2.0 プロバイダーをサポートする ReactiveOAuth2UserService
の実装です。
ReactiveOAuth2UserService は、エンドユーザー(リソース所有者)のユーザー属性を UserInfo エンドポイントから取得し(認可フロー中にクライアントに付与されたアクセストークンを使用して)、AuthenticatedPrincipal を OAuth2User の形式で返します。 |
DefaultReactiveOAuth2UserService
は、UserInfo エンドポイントでユーザー属性をリクエストするときに WebClient
を使用します。
UserInfo リクエストの前処理および / または UserInfo レスポンスのリアクティブ処理をカスタマイズする必要がある場合、DefaultReactiveOAuth2UserService.setWebClient()
にカスタム構成の WebClient
を提供する必要があります。
DefaultReactiveOAuth2UserService
をカスタマイズする場合でも、ReactiveOAuth2UserService
の独自の実装を提供する場合でも、次の例に示すように構成する必要があります。
Java
Kotlin
@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
...
.oauth2Login(withDefaults());
return http.build();
}
@Bean
public ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
...
}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
oauth2Login { }
}
return http.build()
}
@Bean
fun oauth2UserService(): ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> {
// ...
}
}
OpenID Connect 1.0 UserService
OidcReactiveOAuth2UserService
は、OpenID Connect 1.0 プロバイダーをサポートする ReactiveOAuth2UserService
の実装です。
OidcReactiveOAuth2UserService
は、UserInfo エンドポイントでユーザー属性をリクエストするときに DefaultReactiveOAuth2UserService
を活用します。
UserInfo リクエストの前処理および / または UserInfo レスポンスのリアクティブ処理をカスタマイズする必要がある場合、OidcReactiveOAuth2UserService.setOauth2UserService()
にカスタム構成の ReactiveOAuth2UserService
を提供する必要があります。
OidcReactiveOAuth2UserService
をカスタマイズするか、OpenID Connect 1.0 プロバイダーの ReactiveOAuth2UserService
の独自の実装を提供するかどうかにかかわらず、次の例に示すように構成する必要があります。
Java
Kotlin
@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
...
.oauth2Login(withDefaults());
return http.build();
}
@Bean
public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
...
}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
oauth2Login { }
}
return http.build()
}
@Bean
fun oidcUserService(): ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
// ...
}
}
ID トークン署名検証
OpenID Connect 1.0 認証は ID トークン (英語) を導入します。ID トークン (英語) は、クライアントが使用する場合の認可サーバーによるエンドユーザーの認証に関するクレームを含むセキュリティトークンです。
ID トークンは JSON Web トークン [IETF] (英語) (JWT)として表され、JSON Web 署名 [IETF] (英語) (JWS)を使用して署名する必要があります。
ReactiveOidcIdTokenDecoderFactory
は、OidcIdToken
署名検証に使用される ReactiveJwtDecoder
を提供します。デフォルトのアルゴリズムは RS256
ですが、クライアントの登録時に割り当てられる場合は異なる場合があります。これらの場合、特定のクライアントに割り当てられた予想される JWS アルゴリズムを返すようにリゾルバーを構成できます。
JWS アルゴリズムリゾルバーは、ClientRegistration
を受け入れ、クライアントに期待される JwsAlgorithm
を返す Function
です。SignatureAlgorithm.RS256
または MacAlgorithm.HS256
次のコードは、すべての ClientRegistration
に対してデフォルトで MacAlgorithm.HS256
になるように OidcIdTokenDecoderFactory
@Bean
を構成する方法を示しています。
Java
Kotlin
@Bean
public ReactiveJwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
ReactiveOidcIdTokenDecoderFactory idTokenDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> MacAlgorithm.HS256);
return idTokenDecoderFactory;
}
@Bean
fun idTokenDecoderFactory(): ReactiveJwtDecoderFactory<ClientRegistration> {
val idTokenDecoderFactory = ReactiveOidcIdTokenDecoderFactory()
idTokenDecoderFactory.setJwsAlgorithmResolver { MacAlgorithm.HS256 }
return idTokenDecoderFactory
}
HS256 、HS384 または HS512 などの MAC ベースのアルゴリズムの場合、client-id に対応する client-secret が署名検証の対称キーとして使用されます。 |
OpenID Connect 1.0 認証用に複数の ClientRegistration が構成されている場合、JWS アルゴリズムリゾルバーは提供された ClientRegistration を評価して、返すアルゴリズムを決定します。 |
次に、ログアウトの構成に進むことができます。