跳到主要内容
版本:7.0.2

OAuth2

DeepSeek V3 中英对照 OAuth2

Spring Security 提供了全面的 OAuth 2.0 支持。本节将讨论如何将 OAuth 2.0 集成到基于 servlet 的应用程序中。

概述​

Spring Security 的 OAuth 2.0 支持主要由三个核心功能集构成:

备注

OAuth2 登录 是一个非常强大的 OAuth2 客户端功能,值得在参考文档中拥有独立的章节。然而,它并非作为一个独立的功能存在,需要 OAuth2 客户端才能运行。

这些功能集涵盖了OAuth 2.0授权框架中定义的资源服务器、客户端和授权服务器角色。

在OAuth2中,资源服务器和客户端角色通常由一个或多个服务器端应用程序代表。此外,授权服务器角色可以由一个或多个第三方代表(例如在组织内集中身份管理和/或认证时的情况)-或- 也可以由一个应用程序代表(例如使用授权服务器功能时的情况)。

例如,一个典型的基于OAuth2的微服务架构可能包含一个面向用户的客户端应用程序、多个提供REST API的后端资源服务器,以及一个用于管理用户和身份验证的第三方授权服务器。同样常见的情况是,单个应用程序仅代表其中一个角色,需要与提供其他角色的一个或多个第三方进行集成。

Spring Security 能够处理这些场景以及更多情况。以下章节将介绍 Spring Security 提供的角色,并包含常见场景的示例。

OAuth2 资源服务器​

备注

本节包含 OAuth2 资源服务器功能的概览及示例。完整参考文档请参阅 OAuth 2.0 资源服务器。

要开始使用,请将 spring-security-oauth2-resource-server 依赖添加到你的项目中。如果使用 Spring Boot,请添加以下 starter:

implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
提示

如果不使用 Spring Boot,请参阅 获取 Spring Security 以了解其他选项。

考虑以下OAuth2资源服务器的使用场景:

使用 OAuth2 访问令牌保护访问​

使用 OAuth2 访问令牌保护 API 访问是非常常见的做法。在大多数情况下,Spring Security 只需进行最少的配置即可通过 OAuth2 保护应用程序。

Spring Security 支持两种类型的 Bearer 令牌,每种类型使用不同的组件进行验证:

JWT 支持​

以下示例使用 Spring Boot 配置属性配置了一个 JwtDecoder bean:

spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://my-auth-server.com

在使用Spring Boot时,仅需如此配置即可。Spring Boot提供的默认配置等同于以下设置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}

@Bean
public JwtDecoder jwtDecoder() {
return JwtDecoders.fromIssuerLocation("https://my-auth-server.com");
}

}

不透明令牌支持​

以下示例使用 Spring Boot 配置属性配置了一个 OpaqueTokenIntrospector bean:

spring:
security:
oauth2:
resourceserver:
opaquetoken:
introspection-uri: https://my-auth-server.com/oauth2/introspect
client-id: my-client-id
client-secret: my-client-secret

在使用Spring Boot时,仅需如此配置即可。Spring Boot提供的默认配置等效于以下内容:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.opaqueToken(Customizer.withDefaults())
);
return http.build();
}

@Bean
public OpaqueTokenIntrospector opaqueTokenIntrospector() {
return new SpringOpaqueTokenIntrospector(
"https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret");
}

}

使用自定义 JWT 保护访问​

使用JWT保护API访问是一个相当常见的目标,尤其是在前端开发为单页应用时。Spring Security中的OAuth2资源服务器支持可用于任何类型的Bearer令牌,包括自定义JWT。

使用JWT保护API所需的全部配置仅是一个JwtDecoder Bean,该组件用于验证签名并解码令牌。Spring Security将自动使用提供的Bean在SecurityFilterChain中配置保护机制。

以下示例使用 Spring Boot 配置属性配置了一个 JwtDecoder bean:

spring:
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:my-public-key.pub
备注

你可以将公钥作为类路径资源提供(在本示例中称为 my-public-key.pub)。

在使用Spring Boot时,仅需如此配置即可。Spring Boot提供的默认配置等同于以下设置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}

@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(publicKey()).build();
}

private RSAPublicKey publicKey() {
// ...
}

}
备注

Spring Security 并未提供用于生成令牌的端点。然而,Spring Security 确实提供了 JwtEncoder 接口以及一个实现,即 NimbusJwtEncoder。

OAuth2 客户端​

备注

本节包含 OAuth2 客户端功能的概览及示例。完整参考文档请参阅 OAuth 2.0 客户端 和 OAuth 2.0 登录。

要开始使用,请将 spring-security-oauth2-client 依赖项添加到您的项目中。如果使用 Spring Boot,请添加以下 starter:

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
提示

在不使用 Spring Boot 的情况下,如需更多选项,请参阅 获取 Spring Security。

考虑以下OAuth2客户端的应用场景:

使用 OAuth2 登录用户​

用户通过 OAuth2 登录是非常常见的需求。OpenID Connect 1.0 提供了一种特殊的令牌,称为 id_token,其设计目的是让 OAuth2 客户端能够执行用户身份验证并让用户登录。在某些情况下,也可以直接使用 OAuth2 来让用户登录(例如一些流行的社交登录提供商,如 GitHub 和 Facebook,它们没有实现 OpenID Connect)。

以下示例配置应用程序,使其能够作为 OAuth2 客户端,支持用户通过 OAuth2 或 OpenID Connect 登录:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Login(Customizer.withDefaults());
return http.build();
}

}

除了上述配置外,应用程序还需要通过 ClientRegistrationRepository bean 配置至少一个 ClientRegistration。以下示例使用 Spring Boot 配置属性配置了一个 InMemoryClientRegistrationRepository bean:

spring:
security:
oauth2:
client:
registration:
my-oidc-client:
provider: my-oidc-provider
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: openid,profile
provider:
my-oidc-provider:
issuer-uri: https://my-oidc-provider.com

通过以上配置,应用程序现在支持两个额外的端点:

  1. 登录端点(例如 /oauth2/authorization/my-oidc-client)用于发起登录并重定向到第三方授权服务器。

  2. 重定向端点(例如 /login/oauth2/code/my-oidc-client)由授权服务器用于重定向回客户端应用程序,并将包含一个 code 参数,该参数用于通过访问令牌请求获取 id_token 和/或 access_token。

备注

上述配置中包含 openid 作用域表明应使用 OpenID Connect 1.0。这会指示 Spring Security 在请求处理期间使用 OIDC 专用组件(例如 OidcUserService)。若未包含此作用域,Spring Security 将转而使用 OAuth2 专用组件(例如 DefaultOAuth2UserService)。

访问受保护资源​

向受 OAuth2 保护的第三方 API 发起请求是 OAuth2 Client 的核心使用场景。这通常通过授权一个客户端(在 Spring Security 中由 OAuth2AuthorizedClient 类表示)来实现,并在对外请求的 Authorization 头部中放置一个 Bearer 令牌来访问受保护的资源。

以下示例配置了应用程序,使其能够作为 OAuth2 客户端,向第三方 API 请求受保护的资源:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Client(Customizer.withDefaults());
return http.build();
}

}
备注

上述示例并未提供用户登录的方式。你可以使用任何其他登录机制(例如 formLogin())。关于将 oauth2Client() 与 oauth2Login() 结合使用的示例,请参阅下一节。

除了上述配置外,应用程序还需要通过ClientRegistrationRepository bean配置至少一个ClientRegistration。以下示例使用Spring Boot配置属性配置了一个InMemoryClientRegistrationRepository bean:

spring:
security:
oauth2:
client:
registration:
my-oauth2-client:
provider: my-auth-server
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: message.read,message.write
provider:
my-auth-server:
issuer-uri: https://my-auth-server.com

除了配置Spring Security以支持OAuth2客户端功能外,您还需要决定如何访问受保护资源,并相应配置您的应用程序。Spring Security提供了OAuth2AuthorizedClientManager的实现,用于获取可用于访问受保护资源的访问令牌。

提示

当不存在 OAuth2AuthorizedClientManager 的 bean 时,Spring Security 会为你注册一个默认的实例。

使用 OAuth2AuthorizedClientManager 最简单的方式是通过 ClientHttpRequestInterceptor,它通过 RestClient 拦截请求,当 spring-web 在类路径上时,RestClient 已经可用。

以下示例使用默认的 OAuth2AuthorizedClientManager 来配置一个 RestClient,该客户端能够通过在每个请求的 Authorization 头部放置 Bearer 令牌来访问受保护资源:

@Configuration
public class RestClientConfig {

@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}

}

配置好的 RestClient 可按以下示例使用:

import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;

@RestController
public class MessagesController {

private final RestClient restClient;

public MessagesController(RestClient restClient) {
this.restClient = restClient;
}

@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
Message[] messages = this.restClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.body(Message[].class);
return ResponseEntity.ok(Arrays.asList(messages));
}

public record Message(String message) {
}

}

使用 WebClient 访问受保护资源​

向受 OAuth2 保护的第三方 API 发起请求是 OAuth2 Client 的核心应用场景。这一过程通过授权客户端(在 Spring Security 中由 OAuth2AuthorizedClient 类表示)实现,并在对外请求的 Authorization 头部中放置 Bearer 令牌来访问受保护的资源。

以下示例配置了应用程序,使其能够作为 OAuth2 客户端,向第三方 API 请求受保护的资源:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Client(Customizer.withDefaults());
return http.build();
}

}
备注

上述示例并未提供用户登录的方式。您可以使用任何其他登录机制(例如 formLogin())。有关将 oauth2Client() 与 oauth2Login() 结合使用的示例,请参阅上一节。

除了上述配置外,应用程序还需要通过ClientRegistrationRepository bean配置至少一个ClientRegistration。以下示例使用Spring Boot配置属性配置了一个InMemoryClientRegistrationRepository bean:

spring:
security:
oauth2:
client:
registration:
my-oauth2-client:
provider: my-auth-server
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: message.read,message.write
provider:
my-auth-server:
issuer-uri: https://my-auth-server.com

除了配置Spring Security以支持OAuth2客户端功能外,您还需要决定如何访问受保护资源,并相应配置您的应用程序。Spring Security提供了OAuth2AuthorizedClientManager的实现,用于获取可用于访问受保护资源的访问令牌。

提示

当不存在 OAuth2AuthorizedClientManager 的 bean 时,Spring Security 会为你注册一个默认的 bean。

除了配置 RestClient,另一种使用 OAuth2AuthorizedClientManager 的方式是通过 ExchangeFilterFunction,它通过 WebClient 拦截请求。要使用 WebClient,你需要添加 spring-webflux 依赖以及一个响应式客户端实现:

implementation 'org.springframework:spring-webflux'
implementation 'io.projectreactor.netty:reactor-netty'

以下示例使用默认的 OAuth2AuthorizedClientManager 来配置一个 WebClient,该客户端能够通过在每个请求的 Authorization 头部放置 Bearer 令牌来访问受保护资源:

@Configuration
public class WebClientConfig {

@Bean
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction filter =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(filter.oauth2Configuration())
.build();
}

}

配置好的 WebClient 可按以下示例使用:

import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;

@RestController
public class MessagesController {

private final WebClient webClient;

public MessagesController(WebClient webClient) {
this.webClient = webClient;
}

@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
return this.webClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.toEntityList(Message.class)
.block();
}

public record Message(String message) {
}

}

访问当前用户的受保护资源​

当用户通过OAuth2或OpenID Connect登录时,授权服务器可能会提供一个访问令牌,该令牌可直接用于访问受保护资源。这种方式非常方便,因为它只需要为这两种用例同时配置一个 ClientRegistration。

备注

本节将使用 OAuth2 登录用户和访问受保护资源的配置合并为一个单一配置。还存在其他高级场景,例如配置一个 ClientRegistration 用于登录,另一个用于访问受保护资源。所有这些场景都将使用相同的基础配置。

以下示例配置了应用程序,使其能够作为 OAuth2 客户端,既允许用户登录,又能够向第三方 API 请求受保护的资源:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Login(Customizer.withDefaults())
.oauth2Client(Customizer.withDefaults());
return http.build();
}

}

除了上述配置外,应用程序还需要通过 ClientRegistrationRepository Bean 至少配置一个 ClientRegistration。以下示例使用 Spring Boot 配置属性配置了一个 InMemoryClientRegistrationRepository Bean:

spring:
security:
oauth2:
client:
registration:
my-combined-client:
provider: my-auth-server
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: openid,profile,message.read,message.write
provider:
my-auth-server:
issuer-uri: https://my-auth-server.com
备注

之前示例(使用 OAuth2 登录用户、访问受保护资源)与本示例的主要区别在于通过 scope 属性配置的内容,该属性将标准范围 openid 和 profile 与自定义范围 message.read 和 message.write 相结合。

除了配置Spring Security以支持OAuth2客户端功能外,您还需要决定如何访问受保护资源,并相应配置您的应用程序。Spring Security提供了OAuth2AuthorizedClientManager的实现,用于获取可用于访问受保护资源的访问令牌。

提示

当不存在 OAuth2AuthorizedClientManager 的 bean 时,Spring Security 会为你注册一个默认的 bean。

使用 OAuth2AuthorizedClientManager 最简单的方式是通过 ClientHttpRequestInterceptor,它通过 RestClient 拦截请求,当 spring-web 在类路径上时,RestClient 已经可用。

以下示例使用默认的 OAuth2AuthorizedClientManager 来配置一个 RestClient,该客户端能够通过在每个请求的 Authorization 头部放置 Bearer 令牌来访问受保护资源:

@Configuration
public class RestClientConfig {

@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
requestInterceptor.setClientRegistrationIdResolver(clientRegistrationIdResolver());

return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}

private static ClientRegistrationIdResolver clientRegistrationIdResolver() {
return (request) -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return (authentication instanceof OAuth2AuthenticationToken principal)
? principal.getAuthorizedClientRegistrationId()
: null;
};
}

}

配置好的 RestClient 可按以下示例使用:

@RestController
public class MessagesController {

private final RestClient restClient;

public MessagesController(RestClient restClient) {
this.restClient = restClient;
}

@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
Message[] messages = this.restClient.get()
.uri("http://localhost:8090/messages")
.retrieve()
.body(Message[].class);
return ResponseEntity.ok(Arrays.asList(messages));
}

public record Message(String message) {
}

}
备注

与之前的示例不同,请注意我们无需告知 Spring Security 要使用的 clientRegistrationId。这是因为该信息可以从当前已登录用户中推导出来。

使用客户端凭证授权​

备注

本节重点介绍客户端凭证授权类型的额外注意事项。有关所有授权类型的通用设置和使用方法,请参阅访问受保护资源。

客户端凭证授权允许客户端代表自身获取 access_token。客户端凭证授权是一种简单的流程,不涉及资源所有者(即用户)。

注意

需要注意的是,客户端凭证许可的典型使用意味着任何请求(或用户)都有可能获取访问令牌,并向资源服务器发起受保护资源的请求。在设计应用程序时务必谨慎,确保用户无法发起未经授权的请求,因为每个请求都能够获取访问令牌。

在允许用户登录的Web应用程序中获取访问令牌时,Spring Security的默认行为是为每个用户获取一个访问令牌。

备注

默认情况下,访问令牌的作用域限定为当前用户的主体名称,这意味着每个用户都将获得一个唯一的访问令牌。

使用客户端凭据授权的客户端通常要求访问令牌的作用域限定于应用程序而非单个用户,因此每个应用程序仅有一个访问令牌。若要将访问令牌的作用域限定于应用程序,您需要设置解析自定义主体名称的策略。以下示例通过配置带有 RequestAttributePrincipalResolver 的 RestClient 实现此功能:

@Configuration
public class RestClientConfig {

@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
requestInterceptor.setPrincipalResolver(new RequestAttributePrincipalResolver());
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}

}

配置完成后,即可为每个请求指定主体名称。以下示例演示如何通过指定主体名称将访问令牌限定在应用程序范围内:

import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;
import static org.springframework.security.oauth2.client.web.client.RequestAttributePrincipalResolver.principal;

@RestController
public class MessagesController {

private final RestClient restClient;

public MessagesController(RestClient restClient) {
this.restClient = restClient;
}

@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
Message[] messages = this.restClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.attributes(principal("my-application"))
.retrieve()
.body(Message[].class);
return ResponseEntity.ok(Arrays.asList(messages));
}

public record Message(String message) {
}

}
备注

在上述示例中,通过属性指定主体名称时,将仅生成单个访问令牌,并且该令牌将用于所有请求。

启用扩展授权类型​

一个常见的用例涉及启用和/或配置扩展授权类型。例如,Spring Security 提供了对 jwt-bearer 和 token-exchange 授权类型的支持,但默认情况下并未启用它们,因为它们不属于 OAuth 2.0 核心规范的一部分。

从 Spring Security 6.2 版本开始,我们可以简单地发布一个或多个 OAuth2AuthorizedClientProvider 的 Bean,它们将被自动识别。以下示例仅启用了 jwt-bearer 授权类型:

@Configuration
public class SecurityConfig {

@Bean
public OAuth2AuthorizedClientProvider jwtBearer() {
return new JwtBearerOAuth2AuthorizedClientProvider();
}

}

当没有提供默认的 OAuth2AuthorizedClientManager 时,Spring Security 会自动发布一个。

提示

任何自定义的 OAuth2AuthorizedClientProvider bean 也会被自动发现,并在默认授权类型之后应用到所提供的 OAuth2AuthorizedClientManager 中。

为了在Spring Security 6.2之前实现上述配置,我们必须自行发布这个bean,并确保重新启用默认授权类型。要了解背后配置的具体内容,以下展示了可能的配置示例:

@Configuration
public class SecurityConfig {

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {

OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.provider(new JwtBearerOAuth2AuthorizedClientProvider())
.build();

DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

return authorizedClientManager;
}

}

自定义现有授权类型​

通过发布一个 bean 来启用扩展授权类型的能力,也提供了在不重新定义默认配置的情况下自定义现有授权类型的机会。例如,如果我们想为 client_credentials 授权自定义 OAuth2AuthorizedClientProvider 的时钟偏差,我们可以简单地发布一个这样的 bean:

@Configuration
public class SecurityConfig {

@Bean
public OAuth2AuthorizedClientProvider clientCredentials() {
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider =
new ClientCredentialsOAuth2AuthorizedClientProvider();
authorizedClientProvider.setClockSkew(Duration.ofMinutes(5));

return authorizedClientProvider;
}

}

自定义令牌请求参数​

在获取访问令牌时,自定义请求参数的需求相当常见。例如,假设我们想要在令牌请求中添加一个自定义的 audience 参数,因为提供程序要求在 authorization_code 授权流程中包含此参数。

从 Spring Security 6.2 版本开始,我们只需发布一个泛型类型为 OAuth2AuthorizationCodeGrantRequest 的 OAuth2AccessTokenResponseClient Bean,Spring Security 就会使用它来配置 OAuth2 客户端组件。

以下示例展示了如何在不使用DSL的情况下,为authorization_code授权类型自定义令牌请求参数:

@Configuration
public class SecurityConfig {

@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new RestClientAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.addParametersConverter(parametersConverter());

return accessTokenResponseClient;
}

private static Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter() {
return (grantRequest) -> {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set("audience", "xyz_value");

return parameters;
};
}

}
提示

请注意,在这种情况下我们不需要自定义 SecurityFilterChain bean,可以继续使用默认配置。如果使用 Spring Boot 且没有额外的自定义配置,实际上我们可以完全省略 SecurityFilterChain bean。

在Spring Security 6.2版本之前,我们必须确保通过Spring Security DSL将此自定义配置同时应用于OAuth2登录(如果使用此功能)和OAuth2客户端组件。为了理解其背后的配置机制,以下展示了可能的配置示例:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new RestClientAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.addParametersConverter(parametersConverter());

http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2Login((oauth2Login) -> oauth2Login
.tokenEndpoint((tokenEndpoint) -> tokenEndpoint
.accessTokenResponseClient(accessTokenResponseClient)
)
)
.oauth2Client((oauth2Client) -> oauth2Client
.authorizationCodeGrant((authorizationCode) -> authorizationCode
.accessTokenResponseClient(accessTokenResponseClient)
)
);

return http.build();
}

private static Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter() {
// ...
}

}

对于其他授权类型,我们可以发布额外的 OAuth2AccessTokenResponseClient Bean 来覆盖默认配置。例如,要自定义 client_credentials 授权类型的令牌请求,我们可以发布以下 Bean:

@Configuration
public class SecurityConfig {

@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
new RestClientClientCredentialsTokenResponseClient();
accessTokenResponseClient.addParametersConverter(parametersConverter());

return accessTokenResponseClient;
}

private static Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> parametersConverter() {
// ...
}

}

Spring Security 会自动解析以下泛型类型的 OAuth2AccessTokenResponseClient Bean:

  • OAuth2AuthorizationCodeGrantRequest (参见 RestClientAuthorizationCodeTokenResponseClient)

  • OAuth2RefreshTokenGrantRequest (参见 RestClientRefreshTokenTokenResponseClient)

  • OAuth2ClientCredentialsGrantRequest (参见 RestClientClientCredentialsTokenResponseClient)

  • JwtBearerGrantRequest (参见 RestClientJwtBearerTokenResponseClient)

  • TokenExchangeGrantRequest (参见 RestClientTokenExchangeTokenResponseClient)

提示

发布一个类型为 OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> 的 bean 将自动启用 jwt-bearer 授权类型,而无需单独配置。

提示

发布一个类型为 OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> 的 bean 将自动启用 token-exchange 授权类型,而无需单独配置。

自定义 OAuth2 客户端组件使用的 RestClient​

另一个常见用例是在获取访问令牌时需要自定义所使用的 RestClient。我们可能需要这样做,以自定义响应的处理(通过自定义的 HttpMessageConverter)或为企业网络应用代理设置(通过自定义的 ClientHttpRequestFactory)。

从 Spring Security 6.2 版本开始,我们可以简单地发布 OAuth2AccessTokenResponseClient 类型的 Bean,Spring Security 将自动为我们配置并发布一个 OAuth2AuthorizedClientManager Bean。

以下示例为所有支持的授权类型自定义 RestClient:

@Configuration
public class SecurityConfig {

@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new RestClientAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestClient(restClient());

return accessTokenResponseClient;
}

@Bean
public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
RestClientRefreshTokenTokenResponseClient accessTokenResponseClient =
new RestClientRefreshTokenTokenResponseClient();
accessTokenResponseClient.setRestClient(restClient());

return accessTokenResponseClient;
}

@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
new RestClientClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRestClient(restClient());

return accessTokenResponseClient;
}

@Bean
public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
RestClientJwtBearerTokenResponseClient accessTokenResponseClient =
new RestClientJwtBearerTokenResponseClient();
accessTokenResponseClient.setRestClient(restClient());

return accessTokenResponseClient;
}

@Bean
public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
RestClientTokenExchangeTokenResponseClient accessTokenResponseClient =
new RestClientTokenExchangeTokenResponseClient();
accessTokenResponseClient.setRestClient(restClient());

return accessTokenResponseClient;
}

@Bean
public RestClient restClient() {
// ...
}

}

当没有提供默认的 OAuth2AuthorizedClientManager 时,Spring Security 会自动发布一个。

提示

请注意,在这种情况下我们不需要自定义 SecurityFilterChain bean,可以沿用默认配置。如果使用 Spring Boot 且没有额外的自定义需求,实际上可以完全省略 SecurityFilterChain bean。

在Spring Security 6.2版本之前,我们必须确保此自定义配置同时应用于OAuth2登录(如果使用此功能)和OAuth2客户端组件。我们需要同时使用Spring Security DSL(针对authorization_code授权类型)并为其他授权类型发布一个OAuth2AuthorizedClientManager类型的Bean。为了理解背后的配置机制,以下展示了可能的配置示例:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new RestClientAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestClient(restClient());

http
// ...
.oauth2Login((oauth2Login) -> oauth2Login
.tokenEndpoint((tokenEndpoint) -> tokenEndpoint
.accessTokenResponseClient(accessTokenResponseClient)
)
)
.oauth2Client((oauth2Client) -> oauth2Client
.authorizationCodeGrant((authorizationCode) -> authorizationCode
.accessTokenResponseClient(accessTokenResponseClient)
)
);

return http.build();
}

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {

RestClientRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient =
new RestClientRefreshTokenTokenResponseClient();
refreshTokenAccessTokenResponseClient.setRestClient(restClient());

RestClientClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient =
new RestClientClientCredentialsTokenResponseClient();
clientCredentialsAccessTokenResponseClient.setRestClient(restClient());

RestClientJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
new RestClientJwtBearerTokenResponseClient();
jwtBearerAccessTokenResponseClient.setRestClient(restClient());

JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider =
new JwtBearerOAuth2AuthorizedClientProvider();
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient);

RestClientTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient =
new RestClientTokenExchangeTokenResponseClient();
tokenExchangeAccessTokenResponseClient.setRestClient(restClient());

TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
new TokenExchangeOAuth2AuthorizedClientProvider();
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient);

OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken((refreshToken) -> refreshToken
.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
)
.clientCredentials((clientCredentials) -> clientCredentials
.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
)
.provider(jwtBearerAuthorizedClientProvider)
.provider(tokenExchangeAuthorizedClientProvider)
.build();

DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

return authorizedClientManager;
}

@Bean
public RestClient restClient() {
// ...
}

}

延伸阅读​

前面的章节介绍了 Spring Security 对 OAuth2 的支持,并提供了常见场景的示例。您可以在参考文档的以下章节中了解更多关于 OAuth2 客户端、资源服务器和授权服务器的信息:

章节总结​