跳到主要内容

高级配置

QWen Max 中英对照 Advanced Configuration

OAuth 2.0 授权框架将协议端点定义如下:

授权过程使用两个授权服务器端点(HTTP资源):

  • 授权端点:客户端通过用户代理重定向从资源所有者那里获得授权时使用。

  • 令牌端点:客户端用来将授权许可交换为访问令牌,通常需要客户端认证。

还有一个客户端端点:

  • 重定向端点:用于授权服务器通过资源所有者的用户代理将包含授权凭证的响应返回给客户端。

OpenID Connect Core 1.0 规范将 UserInfo 端点 定义如下:

UserInfo 端点是一个受 OAuth 2.0 保护的资源,它返回关于已认证终端用户的信息。为了获取有关终端用户的请求信息,客户端使用通过 OpenID Connect 认证获得的访问令牌向 UserInfo 端点发起请求。这些声明通常由一个 JSON 对象表示,该对象包含声明的名称-值对集合。

ServerHttpSecurity.oauth2Login() 提供了多个配置选项来自定义 OAuth 2.0 登录。

以下代码展示了 oauth2Login() DSL 可用的完整配置选项:

@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();
}
}
java

以下各节将详细介绍每个可用的配置选项:

OAuth 2.0 登录页面

默认情况下,OAuth 2.0 登录页面由 LoginPageGeneratingWebFilter 自动生成。默认登录页面显示每个配置的 OAuth 客户端,并将其 ClientRegistration.clientName 作为链接,该链接能够发起授权请求(或 OAuth 2.0 登录)。

备注

为了让 LoginPageGeneratingWebFilter 显示已配置的 OAuth 客户端的链接,注册的 ReactiveClientRegistrationRepository 还需要实现 Iterable<ClientRegistration>。请参阅 InMemoryReactiveClientRegistrationRepository 作为参考。

每个 OAuth 客户端的链接目标默认为以下内容:

"/oauth2/authorization/{registrationId}"


<Translate>
<TranslateTarget>

以下这行展示了一个示例:

</TranslateTarget>
<TranslateSource>

The following line shows an example:

</TranslateSource>
</Translate>

```html
<a href="/oauth2/authorization/google">Google</a>

要覆盖默认的登录页面,请配置 exceptionHandling().authenticationEntryPoint() 和(可选)oauth2Login().authorizationRequestResolver()

以下列表显示了一个示例:

@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);
}

...
}
java
important

你需要提供一个带有 @RequestMapping("/login/oauth2")@Controller,以便能够渲染自定义登录页面。

提示

如前所述,配置 oauth2Login().authorizationRequestResolver() 是可选的。但是,如果你选择自定义它,请确保每个 OAuth 客户端的链接与通过 ServerWebExchangeMatcher 提供的模式匹配。

以下是一行示例:

<a href="/login/oauth2/authorization/google">Google</a>
html

重定向端点

重定向端点由授权服务器用于通过资源所有者的用户代理将授权响应(其中包含授权凭证)返回给客户端。

提示

OAuth 2.0 登录利用了授权码许可类型。因此,授权凭证是授权码。

默认的授权响应重定向端点是 /login/oauth2/code/{registrationId}

如果您想自定义授权响应重定向端点,请按照以下示例进行配置:

@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();
}
}
java
important

你还需要确保 ClientRegistration.redirectUri 与自定义的授权响应重定向端点相匹配。

以下示例展示了这一点:

return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
.build();
java

UserInfo Endpoint

UserInfo Endpoint 包含多个配置选项,如下所述:

映射用户权限

在用户成功通过 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 以使其自动应用于配置,如以下示例所示:

@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;
};
}
}
java

基于委托的策略与 ReactiveOAuth2UserService

与使用 GrantedAuthoritiesMapper 相比,这种策略更高级,但同时也更加灵活,因为它可以让你访问 OAuth2UserRequestOAuth2User(当使用 OAuth 2.0 UserService 时)或 OidcUserRequestOidcUser(当使用 OpenID Connect 1.0 UserService 时)。

OAuth2UserRequest(和 OidcUserRequest)为你提供了访问相关 OAuth2AccessToken 的途径,在委托人需要从受保护的资源中获取权限信息才能为用户映射自定义权限的情况下,这非常有用。

以下示例展示了如何使用 OpenID Connect 1.0 UserService 实现和配置基于委派的策略:

@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);
});
};
}
}
java

OAuth 2.0 UserService

DefaultReactiveOAuth2UserService 是一个 ReactiveOAuth2UserService 的实现,支持标准的 OAuth 2.0 提供商。

备注

ReactiveOAuth2UserService 从 UserInfo 端点(通过使用在授权流程中授予客户端的访问令牌)获取终端用户(资源所有者)的用户属性,并返回一个以 OAuth2User 形式的 AuthenticatedPrincipal

DefaultReactiveOAuth2UserService 在请求用户信息端点的用户属性时使用 WebClient

如果你需要自定义 UserInfo 请求的预处理和/或 UserInfo 响应的后处理,你需要为 DefaultReactiveOAuth2UserService.setWebClient() 提供一个自定义配置的 WebClient

无论你是自定义 DefaultReactiveOAuth2UserService 还是提供自己的 ReactiveOAuth2UserService 实现,你都需要按照以下示例进行配置:

@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
...
.oauth2Login(withDefaults());

return http.build();
}

@Bean
public ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
...
}
}
java

OpenID Connect 1.0 UserService

OidcReactiveOAuth2UserService 是一个支持 OpenID Connect 1.0 提供者的 ReactiveOAuth2UserService 实现。

OidcReactiveOAuth2UserService 在请求用户信息端点(UserInfo Endpoint)的用户属性时利用了 DefaultReactiveOAuth2UserService

如果你需要自定义 UserInfo 请求的预处理和/或 UserInfo 响应的后处理,你需要向 OidcReactiveOAuth2UserService.setOauth2UserService() 提供一个自定义配置的 ReactiveOAuth2UserService

无论你是自定义 OidcReactiveOAuth2UserService,还是为 OpenID Connect 1.0 提供者提供自己的 ReactiveOAuth2UserService 实现,你都需要按照以下示例进行配置:

@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
...
.oauth2Login(withDefaults());

return http.build();
}

@Bean
public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
...
}
}
java

ID Token 签名验证

OpenID Connect 1.0 身份验证引入了 ID 令牌,这是一种安全令牌,当客户端使用时,它包含由授权服务器对终端用户进行身份验证的声明。

ID Token 以 JSON Web Token(JWT)表示,并且必须使用 JSON Web Signature(JWS)进行签名。

ReactiveOidcIdTokenDecoderFactory 提供了一个用于 OidcIdToken 签名验证的 ReactiveJwtDecoder。默认算法是 RS256,但在客户端注册时可能会分配不同的算法。对于这些情况,可以配置一个解析器来返回为特定客户端分配的预期 JWS 算法。

JWS 算法解析器是一个 Function,它接受一个 ClientRegistration 并为客户端返回预期的 JwsAlgorithm,例如 SignatureAlgorithm.RS256MacAlgorithm.HS256

以下代码展示了如何配置 OidcIdTokenDecoderFactory @Bean 以对所有 ClientRegistration 默认使用 MacAlgorithm.HS256

@Bean
public ReactiveJwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
ReactiveOidcIdTokenDecoderFactory idTokenDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> MacAlgorithm.HS256);
return idTokenDecoderFactory;
}
java
备注

对于基于 MAC 的算法,如 HS256HS384HS512,与 client-id 对应的 client-secret 用作签名验证的对称密钥。

提示

如果为 OpenID Connect 1.0 身份验证配置了多个 ClientRegistration,JWS 算法解析器可能会评估提供的 ClientRegistration 以确定返回哪个算法。

然后,您可以继续配置logout