跳到主要内容

OAuth2

QWen Max 中英对照 OAuth2

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

概述

Spring Security 的 OAuth 2.0 支持主要包括两个主要功能集:

备注

OAuth2 登录 是一个非常强大的 OAuth2 客户端功能,在参考文档中值得单独一节来介绍。但是,它并不是一个独立的功能,需要 OAuth2 客户端才能运行。

这些功能集涵盖了OAuth 2.0 授权框架中定义的资源服务器客户端角色,而授权服务器角色则由Spring 授权服务器涵盖,这是一个基于Spring 安全性构建的单独项目。

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

例如,一个典型的基于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'
gradle
提示

参见 获取 Spring Security,了解在不使用 Spring Boot 时的其他选项。

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

  • 我想使用 OAuth2 保护对 API 的访问(授权服务器提供 JWT 或不透明的访问令牌)(authorization server provides JWT or opaque access token)

  • 我想使用 JWT 保护对 API 的访问(自定义令牌)

使用 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
yaml

在使用 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");
}

}
java

不透明令牌支持

以下示例使用 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
yaml

在使用 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");
}

}
java

使用自定义 JWT 保护访问

保护对 API 的访问使用 JWT 是一个相当常见的目标,特别是当前端被开发为单页应用程序时。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
yaml
备注

您可以将公钥作为类路径资源提供(在本示例中称为 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() {
// ...
}

}
java
备注

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'
gradle
提示

参见 获取 Spring Security,以了解在不使用 Spring Boot 时的其他选项。

请考虑以下 OAuth2 Client 的使用场景:

使用 OAuth2 登录用户

通常需要用户通过 OAuth2 登录。OpenID Connect 1.0 提供了一种特殊的令牌,称为 id_token,它旨在为 OAuth2 客户端提供执行用户身份验证和登录用户的能力。在某些情况下,OAuth2 可以直接用于登录用户(例如,一些流行的社交登录提供商不实现 OpenID Connect,如 GitHub 和 Facebook)。

以下示例将应用程序配置为能够使用 OAuth2 或 OpenID Connect 登录用户的 OAuth2 客户端:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

}
java

除了上述配置之外,应用程序还需要通过使用 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
yaml

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

  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令牌来访问受保护的资源来实现。

以下示例将应用程序配置为能够从第三方 API 请求受保护资源的 OAuth2 客户端:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

}
java
备注

上述示例没有提供登录用户的方法。您可以使用任何其他登录机制(例如 formLogin())。请参见下一节“OAuth 2.0 客户端访问受保护资源 - 当前用户”,以了解结合使用 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
yaml

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

提示

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

使用 OAuth2AuthorizedClientManager 的最简单方法是通过 ClientHttpRequestInterceptor,该拦截器会拦截通过 RestClient 的请求,当 classpath 中包含 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();
}

}
java

此配置的 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) {
}

}
java

使用 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();
}

}
java
备注

上述示例没有提供登录用户的方法。您可以使用任何其他登录机制(例如 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
yaml

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

提示

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

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

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

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

@Configuration
public class WebClientConfig {

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

}
java

此配置的 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) {
}

}
java

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

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

备注

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

以下示例将应用程序配置为能够登录用户并从第三方 API 请求受保护资源的 OAuth2 客户端:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

}
java

除了上述配置之外,应用程序还需要通过使用 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
yaml
备注

前面的示例(使用 OAuth2 登录用户访问受保护的资源)与这个示例之间的主要区别在于通过 scope 属性配置的内容,它将标准范围 openidprofile 与自定义范围 message.readmessage.write 结合在一起。

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

提示

当不存在 OAuth2AuthorizedClientManager 时,Spring Security 会为你注册一个默认的 OAuth2AuthorizedClientManager 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;
};
}

}
java

此配置的 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) {
}

}
java
备注

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

使用客户端凭证授权

备注

本节重点关注客户端凭证授权类型的一些额外注意事项。有关所有授权类型的一般设置和使用,请参见访问受保护的资源

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

注意

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

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

备注

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

使用客户端凭据授权的客户端通常需要访问令牌的作用域限定在应用程序级别,而不是单个用户级别,这样每个应用程序就只有一个访问令牌。为了将访问令牌的作用域限定在应用程序级别,你需要设置一个策略来解析自定义主体名称。以下示例通过配置带有 RequestAttributePrincipalResolverRestClient 来实现这一点:

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

}
java

通过上述配置,可以为每个请求指定主体名称。以下示例演示了如何通过指定主体名称来限定访问令牌的作用域到应用程序:

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) {
}

}
java
备注

通过属性指定主体名称(如上面的示例所示)时,将只有一个访问令牌,并且该令牌将用于所有请求。

启用扩展授权类型

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

从 Spring Security 6.2 及更高版本开始,我们只需发布一个或多个 OAuth2AuthorizedClientProvider 的 bean,它们将被自动识别。以下示例仅启用 jwt-bearer 授权类型:

@Configuration
public class SecurityConfig {

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

}
java

当尚未提供 OAuth2AuthorizedClientManager 时,Spring Security 会自动发布一个默认的 OAuth2AuthorizedClientManager

提示

任何自定义的 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()
.password()
.provider(new JwtBearerOAuth2AuthorizedClientProvider())
.build();

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

return authorizedClientManager;
}

}
java

自定义现有授权类型

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

@Configuration
public class SecurityConfig {

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

return authorizedClientProvider;
}

}
java

自定义令牌请求参数

自定义获取访问令牌时的请求参数是一个相当常见的需求。例如,假设我们希望在令牌请求中添加一个自定义的 audience 参数,因为提供商在 authorization_code 授权类型中要求此参数。

从 Spring Security 6.2 及更高版本开始,我们可以简单地发布一个类型为 OAuth2AccessTokenResponseClient 的 bean,其泛型类型为 OAuth2AuthorizationCodeGrantRequest,Spring Security 将使用它来配置 OAuth2 客户端组件。

以下示例在没有使用 DSL 的情况下自定义 authorization_code 授权类型的令牌请求参数:

@Configuration
public class SecurityConfig {

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

DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);

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

}
java
提示

注意,在这种情况下,我们不需要自定义 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 {
OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
new OAuth2AuthorizationCodeGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(parametersConverter());

DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);

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() {
// ...
}

}
java

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

@Configuration
public class SecurityConfig {

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

DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);

return accessTokenResponseClient;
}

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

}
java

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

  • OAuth2AuthorizationCodeGrantRequest(参见 DefaultAuthorizationCodeTokenResponseClient

  • OAuth2RefreshTokenGrantRequest(参见 DefaultRefreshTokenTokenResponseClient

  • OAuth2ClientCredentialsGrantRequest(参见 DefaultClientCredentialsTokenResponseClient

  • OAuth2PasswordGrantRequest(参见 DefaultPasswordTokenResponseClient

  • JwtBearerGrantRequest(参见 DefaultJwtBearerTokenResponseClient

  • TokenExchangeGrantRequest(参见 DefaultTokenExchangeTokenResponseClient

提示

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

提示

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

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

另一个常见的用例是需要在获取访问令牌时自定义 RestOperations。我们可能需要这样做来定制响应的处理(通过自定义的 HttpMessageConverter)或为公司网络应用代理设置(通过自定义的 ClientHttpRequestFactory)。

从 Spring Security 6.2 及更高版本开始,我们只需发布类型为 OAuth2AccessTokenResponseClient 的 bean,Spring Security 就会为我们配置并发布一个 OAuth2AuthorizedClientManager bean。

以下示例为所有支持的 grant types 自定义 RestOperations

@Configuration
public class SecurityConfig {

@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());

return accessTokenResponseClient;
}

@Bean
public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
DefaultRefreshTokenTokenResponseClient accessTokenResponseClient =
new DefaultRefreshTokenTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());

return accessTokenResponseClient;
}

@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());

return accessTokenResponseClient;
}

@Bean
public OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
DefaultPasswordTokenResponseClient accessTokenResponseClient =
new DefaultPasswordTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());

return accessTokenResponseClient;
}

@Bean
public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
DefaultJwtBearerTokenResponseClient accessTokenResponseClient =
new DefaultJwtBearerTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());

return accessTokenResponseClient;
}

@Bean
public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
DefaultTokenExchangeTokenResponseClient accessTokenResponseClient =
new DefaultTokenExchangeTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());

return accessTokenResponseClient;
}

@Bean
public RestTemplate restTemplate() {
// ...
}

}
java

当尚未提供 OAuth2AuthorizedClientManager 时,Spring Security 会自动发布一个默认的 OAuth2AuthorizedClientManager

提示

注意,在这种情况下,我们不需要自定义 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 {
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());

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) {

DefaultRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient =
new DefaultRefreshTokenTokenResponseClient();
refreshTokenAccessTokenResponseClient.setRestOperations(restTemplate());

DefaultClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate());

DefaultPasswordTokenResponseClient passwordAccessTokenResponseClient =
new DefaultPasswordTokenResponseClient();
passwordAccessTokenResponseClient.setRestOperations(restTemplate());

DefaultJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
new DefaultJwtBearerTokenResponseClient();
jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate());

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

DefaultTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient =
new DefaultTokenExchangeTokenResponseClient();
tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate());

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

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

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

return authorizedClientManager;
}

@Bean
public RestTemplate restTemplate() {
// ...
}

}
java

进一步阅读

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

章节摘要