高级配置
OAuth 2.0 授权框架将 协议端点 定义如下
授权流程使用两个授权服务器端点(HTTP 资源)
-
授权端点:客户端通过用户代理重定向获取资源所有者授权时使用。
-
令牌端点:客户端用于交换授权授予以获取访问令牌,通常与客户端身份验证一起使用。
以及一个客户端端点
-
重定向端点:授权服务器用于通过资源所有者用户代理将包含授权凭据的响应返回给客户端。
OpenID Connect Core 1.0 规范将 用户信息端点 定义如下
用户信息端点是一个 OAuth 2.0 受保护的资源,它返回有关已认证最终用户的声明。为了获取有关最终用户的请求声明,客户端使用通过 OpenID Connect 身份验证获得的访问令牌向用户信息端点发出请求。这些声明通常由一个 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/oauth2/code/{registrationId}
。
如果要自定义授权响应重定向端点,请按照以下示例进行配置
-
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()
}
}
您还需要确保 以下清单显示了一个示例
|
用户信息端点
用户信息端点包含许多配置选项,如下面的子部分所述
映射用户权限
在用户成功使用 OAuth 2.0 提供程序进行身份验证后,OAuth2User.getAuthorities()
(或 OidcUser.getAuthorities()
)包含从 OAuth2UserRequest.getAccessToken().getScopes()
填充并以 SCOPE_
为前缀的授予权限列表。这些授予的权限可以映射到一组新的 GrantedAuthority
实例,在完成身份验证时,这些实例将提供给 OAuth2AuthenticationToken
。
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
是 ReactiveOAuth2UserService
的一个实现,支持标准的 OAuth 2.0 提供商。
ReactiveOAuth2UserService 从用户信息端点(通过使用在授权流程期间授予客户端的访问令牌)获取最终用户(资源所有者)的用户属性,并以 OAuth2User 形式返回一个 AuthenticatedPrincipal 。 |
DefaultReactiveOAuth2UserService
在请求用户信息端点的用户属性时使用 WebClient
。
如果您需要自定义用户信息请求的预处理和/或用户信息响应的后处理,则需要使用自定义配置的 WebClient
提供 DefaultReactiveOAuth2UserService.setWebClient()
。
无论您自定义 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
是 ReactiveOAuth2UserService
的一个实现,支持 OpenID Connect 1.0 提供商。
OidcReactiveOAuth2UserService
在请求用户信息端点的用户属性时利用 DefaultReactiveOAuth2UserService
。
如果您需要自定义用户信息请求的预处理和/或用户信息响应的后处理,则需要使用自定义配置的 ReactiveOAuth2UserService
提供 OidcReactiveOAuth2UserService.setOauth2UserService()
。
无论您自定义 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 令牌表示为JSON Web 令牌(JWT),并且必须使用JSON Web 签名(JWS)进行签名。
ReactiveOidcIdTokenDecoderFactory
提供一个用于 OidcIdToken
签名验证的 ReactiveJwtDecoder
。默认算法为 RS256
,但在客户端注册期间分配时可能有所不同。对于这些情况,可以配置一个解析器以返回为特定客户端分配的预期 JWS 算法。
JWS 算法解析器是一个 Function
,它接受一个 ClientRegistration
并返回客户端的预期 JwsAlgorithm
,例如 SignatureAlgorithm.RS256
或 MacAlgorithm.HS256
以下代码显示如何配置 OidcIdTokenDecoderFactory
@Bean
以将所有 ClientRegistration
的默认值设置为 MacAlgorithm.HS256
-
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
}
对于基于 MAC 的算法(如 HS256 、HS384 或 HS512 ),对应于 client-id 的 client-secret 用作签名验证的对称密钥。 |
如果为 OpenID Connect 1.0 身份验证配置了多个 ClientRegistration ,则 JWS 算法解析器可能会评估提供的 ClientRegistration 以确定要返回哪个算法。 |
然后,您可以继续配置注销。