OAuth2

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

概览

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

OAuth2 登录 是一个非常强大的 OAuth2 Client 特性,在参考文档中有专门的章节介绍。然而,它并非独立特性,需要 OAuth2 Client 才能工作。

这些功能集涵盖了 OAuth 2.0 授权框架 中定义的 资源服务器客户端 角色,而 授权服务器 角色由 Spring Authorization Server 负责,Spring Authorization Server 是一个基于 Spring Security 构建的独立项目。

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

例如,典型的基于 OAuth2 的微服务架构可能由一个面向用户的客户端应用、几个提供 REST API 的后端资源服务器以及一个用于管理用户和认证问题的第三方授权服务器组成。一个应用仅扮演这些角色之一,并需要与提供其他角色的一或多个第三方进行集成,这种情况也很常见。

Spring Security 可以处理这些以及更多场景。以下章节涵盖了 Spring Security 提供的角色,并包含常见场景的示例。

OAuth2 Resource Server

本节总结了 OAuth2 Resource Server 的特性并提供示例。有关完整的参考文档,请参阅 OAuth 2.0 Resource Server

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

OAuth2 Client 与 Spring Boot
  • Gradle

  • Maven

implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

在不使用 Spring Boot 时,请参阅 获取 Spring Security 以了解更多选项。

考虑 OAuth2 Resource Server 的以下用例

使用 OAuth2 Access Token 保护访问

使用 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 提供的默认配置等同于以下内容

使用 JWT 配置 Resource Server
  • Java

  • Kotlin

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

}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			authorizeHttpRequests {
				authorize(anyRequest, authenticated)
			}
			oauth2ResourceServer {
				jwt { }
			}
		}

		return http.build()
	}

	@Bean
	fun 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 提供的默认配置等同于以下内容

使用不透明令牌配置 Resource Server
  • Java

  • Kotlin

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

}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			authorizeHttpRequests {
				authorize(anyRequest, authenticated)
			}
			oauth2ResourceServer {
				opaqueToken { }
			}
		}

		return http.build()
	}

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

}

使用自定义 JWT 保护访问

使用 JWT 保护 API 访问是一个相当普遍的目标,特别是在前端开发为单页应用时。Spring Security 中的 OAuth2 Resource Server 支持可用于任何类型的 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 提供的默认配置等同于以下内容

使用自定义 JWT 配置 Resource Server
  • Java

  • Kotlin

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

}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			authorizeHttpRequests {
				authorize(anyRequest, authenticated)
			}
			oauth2ResourceServer {
				jwt { }
			}
		}

		return http.build()
	}

	@Bean
	fun jwtDecoder(): JwtDecoder {
		return NimbusJwtDecoder.withPublicKey(publicKey()).build()
	}

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

}

Spring Security 不提供用于生成令牌的端点。但是,Spring Security 提供了 JwtEncoder 接口及其一个实现,即 NimbusJwtEncoder

OAuth2 Client

本节总结了 OAuth2 Client 特性并提供示例。有关完整的参考文档,请参阅 OAuth 2.0 ClientOAuth 2.0 登录

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

OAuth2 Client 与 Spring Boot
  • Gradle

  • Maven

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

在不使用 Spring Boot 时,请参阅 获取 Spring Security 以了解更多选项。

考虑 OAuth2 Client 的以下用例

使用 OAuth2 登录用户

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

以下示例配置应用作为 OAuth2 Client,能够使用 OAuth2 或 OpenID Connect 登录用户

配置 OAuth2 登录
  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			// ...
			oauth2Login { }
		}

		return http.build()
	}

}

除了上述配置,应用还需要至少一个 ClientRegistration,这通过使用 ClientRegistrationRepository bean 进行配置。以下示例使用 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 scope 表明应使用 OpenID Connect 1.0。这指示 Spring Security 在请求处理期间使用 OIDC 特定的组件(例如 OidcUserService)。如果缺少此 scope,Spring Security 将转而使用 OAuth2 特定的组件(例如 DefaultOAuth2UserService)。

访问受保护资源

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

以下示例配置应用作为 OAuth2 Client,能够从第三方 API 请求受保护资源

配置 OAuth2 Client
  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			// ...
			oauth2Client { }
		}

		return http.build()
	}

}

上述示例不提供用户登录的方式。您可以使用任何其他登录机制(例如 formLogin())。请参阅下一节,了解结合使用 oauth2Client()oauth2Login() 的示例。

除了上述配置,应用还需要至少一个 ClientRegistration,这通过使用 ClientRegistrationRepository bean 进行配置。以下示例使用 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 Client 特性外,您还需要决定如何访问受保护资源并相应地配置您的应用。Spring Security 提供了 OAuth2AuthorizedClientManager 的实现,用于获取可用于访问受保护资源的访问令牌。

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

使用 OAuth2AuthorizedClientManager 的最简单方法是通过一个 ClientHttpRequestInterceptor,它拦截通过 RestClient 发送的请求。当 spring-web 位于类路径上时,RestClient 已经可用。

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

使用 ClientHttpRequestInterceptor 配置 RestClient
  • Java

  • Kotlin

@Configuration
public class RestClientConfig {

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

}
@Configuration
class RestClientConfig {

	@Bean
	fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
		val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
		return RestClient.builder()
			.requestInterceptor(requestInterceptor)
			.build()
	}

}

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

使用 RestClient 访问受保护资源
  • Java

  • Kotlin

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

}
import org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId
import org.springframework.web.client.body

@RestController
class MessagesController(private val restClient: RestClient) {

	@GetMapping("/messages")
	fun messages(): ResponseEntity<List<Message>> {
		val messages = restClient.get()
			.uri("http://localhost:8090/messages")
			.attributes(clientRegistrationId("my-oauth2-client"))
			.retrieve()
			.body<Array<Message>>()!!
			.toList()
		return ResponseEntity.ok(messages)
	}

	data class Message(val message: String)

}

使用 WebClient 访问受保护资源

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

以下示例配置应用作为 OAuth2 Client,能够从第三方 API 请求受保护资源

配置 OAuth2 Client
  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			// ...
			oauth2Client { }
		}

		return http.build()
	}

}

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

除了上述配置,应用还需要至少一个 ClientRegistration,这通过使用 ClientRegistrationRepository bean 进行配置。以下示例使用 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 Client 特性外,您还需要决定如何访问受保护资源并相应地配置您的应用。Spring Security 提供了 OAuth2AuthorizedClientManager 的实现,用于获取可用于访问受保护资源的访问令牌。

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

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

添加 Spring WebFlux 依赖
  • Gradle

  • Maven

implementation 'org.springframework:spring-webflux'
implementation 'io.projectreactor.netty:reactor-netty'
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
	<groupId>io.projectreactor.netty</groupId>
	<artifactId>reactor-netty</artifactId>
</dependency>

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

使用 ExchangeFilterFunction 配置 WebClient
  • Java

  • Kotlin

@Configuration
public class WebClientConfig {

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

}
@Configuration
class WebClientConfig {

	@Bean
	fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager): WebClient {
		val filter = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
		return WebClient.builder()
			.apply(filter.oauth2Configuration())
			.build()
	}

}

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

使用 WebClient 访问受保护资源
  • Java

  • Kotlin

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

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

@RestController
class MessagesController(private val webClient: WebClient) {

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

	data class Message(val message: String)

}

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

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

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

以下示例配置应用作为 OAuth2 Client,能够登录用户 从第三方 API 请求受保护资源

配置 OAuth2 登录和 OAuth2 Client
  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			// ...
			oauth2Login { }
			oauth2Client { }
		}

		return http.build()
	}

}

除了上述配置,应用还需要至少一个 ClientRegistration,这通过使用 ClientRegistrationRepository bean 进行配置。以下示例使用 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 属性配置的内容,本例将标准 scope openidprofile 与自定义 scope message.readmessage.write 结合。

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

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

使用 OAuth2AuthorizedClientManager 的最简单方法是通过一个 ClientHttpRequestInterceptor,它拦截通过 RestClient 发送的请求。当 spring-web 位于类路径上时,RestClient 已经可用。

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

使用 ClientHttpRequestInterceptor 配置 RestClient
  • Java

  • Kotlin

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

}
@Configuration
class RestClientConfig {

	@Bean
	fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
		val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
		requestInterceptor.setClientRegistrationIdResolver(clientRegistrationIdResolver())

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

	private fun clientRegistrationIdResolver(): OAuth2ClientHttpRequestInterceptor.ClientRegistrationIdResolver {
		return OAuth2ClientHttpRequestInterceptor.ClientRegistrationIdResolver { request ->
			val authentication = SecurityContextHolder.getContext().authentication
			if (authentication is OAuth2AuthenticationToken) {
				authentication.authorizedClientRegistrationId
			} else {
				null
			}
		}
	}

}

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

使用 RestClient 访问受保护资源(当前用户)
  • Java

  • Kotlin

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

}
import org.springframework.web.client.body

@RestController
class MessagesController(private val restClient: RestClient) {

	@GetMapping("/messages")
	fun messages(): ResponseEntity<List<Message>> {
		val messages = restClient.get()
			.uri("http://localhost:8090/messages")
			.retrieve()
			.body<Array<Message>>()!!
			.toList()
		return ResponseEntity.ok(messages)
	}

	data class Message(val message: String)

}

上一个示例不同,请注意我们无需告知 Spring Security 我们希望使用的 clientRegistrationId。这是因为它可以从当前登录的用户中派生出来。

使用客户端凭证授权类型

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

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

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

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

默认情况下,访问令牌的作用域限定在当前用户的 principal name,这意味着每个用户都将收到一个唯一的访问令牌。

使用客户端凭证授权的客户端通常要求访问令牌的作用域限定在应用级别,而不是针对单个用户,这样每个应用只有一个访问令牌。为了将访问令牌的作用域限定在应用级别,您需要设置一个用于解析自定义 principal name 的策略。以下示例通过使用 RequestAttributePrincipalResolver 配置 RestClient 来实现这一点

client_credentials 配置 RestClient
  • Java

  • Kotlin

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

}
@Configuration
class RestClientConfig {

	@Bean
	fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
		val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
		requestInterceptor.setPrincipalResolver(RequestAttributePrincipalResolver())
		return RestClient.builder()
			.requestInterceptor(requestInterceptor)
			.build()
	}

}

通过上述配置,可以为每个请求指定一个 principal name。以下示例演示了如何通过指定 principal name 将访问令牌的作用域限定在应用级别

将访问令牌作用域限定在应用级别
  • Java

  • Kotlin

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

}
import org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId
import org.springframework.security.oauth2.client.web.client.RequestAttributePrincipalResolver.principal
import org.springframework.web.client.body

@RestController
class MessagesController(private val restClient: RestClient) {

	@GetMapping("/messages")
	fun messages(): ResponseEntity<List<Message>> {
		val messages = restClient.get()
			.uri("http://localhost:8090/messages")
			.attributes(clientRegistrationId("my-oauth2-client"))
			.attributes(principal("my-application"))
			.retrieve()
			.body<Array<Message>>()!!
			.toList()
		return ResponseEntity.ok(messages)
	}

	data class Message(val message: String)

}

如上例所示,通过属性指定 principal name 时,将只有一个访问令牌,并且该令牌将用于所有请求。

启用扩展授权类型

启用和/或配置扩展授权类型是一个常见的用例。例如,Spring Security 支持 jwt-bearertoken-exchange 授权类型,但默认不启用它们,因为它们不是核心 OAuth 2.0 规范的一部分。

使用 Spring Security 6.2 及更高版本,我们可以简单地发布一个或多个 OAuth2AuthorizedClientProvider 的 bean,它们将被自动识别。以下示例仅启用 jwt-bearer 授权类型

启用 jwt-bearer 授权类型
  • Java

  • Kotlin

@Configuration
public class SecurityConfig {

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

}
@Configuration
class SecurityConfig {

	@Bean
	fun jwtBearer(): OAuth2AuthorizedClientProvider {
		return JwtBearerOAuth2AuthorizedClientProvider()
	}

}

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

任何自定义的 OAuth2AuthorizedClientProvider bean 也将被识别,并在默认授权类型之后应用于提供的 OAuth2AuthorizedClientManager

为了在 Spring Security 6.2 之前实现上述配置,我们必须自己发布此 bean,并确保也重新启用默认授权类型。为了理解幕后配置的内容,配置可能如下所示

启用 jwt-bearer 授权类型(6.2 之前)
  • Java

  • Kotlin

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

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizedClientManager(
		clientRegistrationRepository: ClientRegistrationRepository,
		authorizedClientRepository: OAuth2AuthorizedClientRepository
	): OAuth2AuthorizedClientManager {
		val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
			.authorizationCode()
			.refreshToken()
			.clientCredentials()
			.password()
			.provider(JwtBearerOAuth2AuthorizedClientProvider())
			.build()

		val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
			clientRegistrationRepository, authorizedClientRepository
		)
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

		return authorizedClientManager
	}

}

自定义现有授权类型

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

自定义客户端凭证授权类型
  • Java

  • Kotlin

@Configuration
public class SecurityConfig {

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

		return authorizedClientProvider;
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun clientCredentials(): OAuth2AuthorizedClientProvider {
		val authorizedClientProvider = ClientCredentialsOAuth2AuthorizedClientProvider()
		authorizedClientProvider.setClockSkew(Duration.ofMinutes(5))
		return authorizedClientProvider
	}

}

自定义令牌请求参数

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

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

以下示例在不使用 DSL 的情况下自定义 authorization_code 授权的令牌请求参数

自定义授权码授权的令牌请求参数
  • Java

  • Kotlin

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

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter()
		requestEntityConverter.addParametersConverter(parametersConverter())

		val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter)

		return accessTokenResponseClient
	}

	private fun parametersConverter(): Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> {
		return Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> { grantRequest ->
			LinkedMultiValueMap<String, String>().also { parameters ->
				parameters["audience"] = "xyz_value"
			}
		}
	}

}

请注意,在这种情况下,我们不需要自定义 SecurityFilterChain bean,并且可以保留默认设置。如果使用 Spring Boot 且没有其他自定义,我们实际上可以完全省略 SecurityFilterChain bean。

在 Spring Security 6.2 之前,我们必须确保此自定义应用于 OAuth2 Login(如果使用此功能)和 OAuth2 Client 组件,使用 Spring Security DSL。为了理解幕后配置的内容,配置可能如下所示

自定义授权码授权的令牌请求参数(6.2 之前)
  • Java

  • Kotlin

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

}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter()
		requestEntityConverter.addParametersConverter(parametersConverter())

		val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
		tokenResponseClient.setRequestEntityConverter(requestEntityConverter)

		http {
			authorizeHttpRequests {
				authorize(anyRequest, authenticated)
			}
			oauth2Login {
				tokenEndpoint {
					accessTokenResponseClient = tokenResponseClient
				}
			}
			oauth2Client {
				authorizationCodeGrant {
					accessTokenResponseClient = tokenResponseClient
				}
			}
		}

		return http.build()
	}

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

}

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

自定义客户端凭证授权的令牌请求参数
  • Java

  • Kotlin

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

}
@Configuration
class SecurityConfig {

	@Bean
	fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
		val requestEntityConverter = OAuth2ClientCredentialsGrantRequestEntityConverter()
		requestEntityConverter.addParametersConverter(parametersConverter())

		val accessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
		accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter)

		return accessTokenResponseClient
	}

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

}

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 Client 组件使用的 RestOperations

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

使用 Spring Security 6.2 及更高版本,我们可以简单地发布类型为 OAuth2AccessTokenResponseClient 的 bean,Spring Security 将为我们配置并发布一个 OAuth2AuthorizedClientManager bean。

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

自定义 OAuth2 Client 的 RestOperations
  • Java

  • Kotlin

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

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun refreshTokenAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
		val accessTokenResponseClient = DefaultRefreshTokenTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
		val accessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun passwordAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
		val accessTokenResponseClient = DefaultPasswordTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
		val accessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun tokenExchangeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
		val accessTokenResponseClient = DefaultTokenExchangeTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun restTemplate(): RestTemplate {
		// ...
	}

}

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

请注意,在这种情况下,我们不需要自定义 SecurityFilterChain bean,并且可以保留默认设置。如果使用 Spring Boot 且没有其他自定义,我们实际上可以完全省略 SecurityFilterChain bean。

在 Spring Security 6.2 之前,我们必须确保此自定义应用于 OAuth2 Login(如果使用此功能)和 OAuth2 Client 组件。我们必须同时使用 Spring Security DSL(用于 authorization_code 授权)并为其他授权类型发布类型为 OAuth2AuthorizedClientManager 的 bean。为了理解幕后配置的内容,配置可能如下所示

自定义 OAuth2 Client 的 RestOperations(6.2 之前)
  • Java

  • Kotlin

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

}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
		tokenResponseClient.setRestOperations(restTemplate())

		http {
			// ...
			oauth2Login {
				tokenEndpoint {
					accessTokenResponseClient = tokenResponseClient
				}
			}
			oauth2Client {
				authorizationCodeGrant {
					accessTokenResponseClient = tokenResponseClient
				}
			}
		}

		return http.build()
	}

	@Bean
	fun authorizedClientManager(
		clientRegistrationRepository: ClientRegistrationRepository?,
		authorizedClientRepository: OAuth2AuthorizedClientRepository?
	): OAuth2AuthorizedClientManager {
		val refreshTokenAccessTokenResponseClient = DefaultRefreshTokenTokenResponseClient()
		refreshTokenAccessTokenResponseClient.setRestOperations(restTemplate())

		val clientCredentialsAccessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
		clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate())

		val passwordAccessTokenResponseClient = DefaultPasswordTokenResponseClient()
		passwordAccessTokenResponseClient.setRestOperations(restTemplate())

		val jwtBearerAccessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
		jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate())

		val jwtBearerAuthorizedClientProvider = JwtBearerOAuth2AuthorizedClientProvider()
		jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient)

		val tokenExchangeAccessTokenResponseClient = DefaultTokenExchangeTokenResponseClient()
		tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate())

		val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
		tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient)

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

		val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
			clientRegistrationRepository, authorizedClientRepository
		)
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

		return authorizedClientManager
	}

	@Bean
	fun restTemplate(): RestTemplate {
		// ...
	}

}

进一步阅读

前面的章节介绍了 Spring Security 对 OAuth2 的支持以及常见场景的示例。您可以在参考文档的以下章节中阅读有关 OAuth2 Client 和 Resource Server 的更多内容