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 时,添加以下启动器

使用 Spring Boot 的 OAuth2 客户端
  • 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 资源服务器用例

使用 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 提供的默认安排等效于以下内容

使用 JWT 配置资源服务器
  • 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 提供的默认安排等效于以下内容

使用不透明令牌配置资源服务器
  • 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 资源服务器支持可用于任何类型的 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 配置资源服务器
  • 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 客户端

本节包含 OAuth2 客户端功能的摘要和示例。请参阅OAuth 2.0 客户端OAuth 2.0 登录以获取完整的参考文档。

首先,将 spring-security-oauth2-client 依赖项添加到您的项目中。使用 Spring Boot 时,添加以下启动器

使用 Spring Boot 的 OAuth2 客户端
  • 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 客户端用例

使用 OAuth2 登录用户

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

以下示例配置应用程序作为 OAuth2 客户端,能够使用 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()
	}

}

除了上述配置,应用程序还需要通过使用 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 客户端的核心用例。这通过授权客户端(在 Spring Security 中由 OAuth2AuthorizedClient 类表示)并通过在出站请求的 Authorization 标头中放置 Bearer 令牌来访问受保护资源来实现。

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

配置 OAuth2 客户端
  • 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() 的示例,请参阅下一节

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

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

以下示例使用默认的 OAuth2AuthorizedClientManager 来配置 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("https://: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("https://: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 客户端的核心用例。这通过授权客户端(在 Spring Security 中由 OAuth2AuthorizedClient 类表示)并通过在出站请求的 Authorization 标头中放置 Bearer 令牌来访问受保护资源来实现。

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

配置 OAuth2 客户端
  • 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() 的示例,请参阅上一节

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

除了配置 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,它能够通过在每个请求的 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("https://: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("https://:8090/messages")
			.attributes(clientRegistrationId("my-oauth2-client"))
			.retrieve()
			.toEntityList<Message>()
			.block()!!
	}

	data class Message(val message: String)

}

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

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

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

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

配置 OAuth2 登录和 OAuth2 客户端
  • 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()
	}

}

除了上述配置,应用程序还需要通过使用 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 属性配置的内容,它结合了标准范围 openidprofile 以及自定义范围 message.readmessage.write

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

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

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

以下示例使用默认的 OAuth2AuthorizedClientManager 来配置 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("https://: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("https://: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 的默认行为是为每个用户获取一个访问令牌。

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

使用客户端凭据授权的客户端通常要求访问令牌的作用域是应用程序而不是单个用户,因此每个应用程序只有一个访问令牌。为了将访问令牌的作用域限定为应用程序,您需要设置一个解析自定义主体名称的策略。以下示例通过使用 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()
	}

}

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

将访问令牌的作用域限定为应用程序
  • 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("https://: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("https://: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)

}

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

启用扩展授权类型

一个常见的用例是启用和/或配置扩展授权类型。例如,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 将自动发布一个默认的 OAuth2AuthorizedClientManager bean。

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

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

		return authorizedClientManager
	}

}

自定义现有授权类型

通过发布 bean 来启用扩展授权类型的能力还提供了自定义现有授权类型的机会,而无需重新定义默认值。例如,如果我们要自定义 client_credentials 授权类型的 OAuth2AuthorizedClientProvider 的时钟偏差,我们可以简单地发布一个 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 及更高版本开始,我们可以简单地发布一个泛型类型为 OAuth2AuthorizationCodeGrantRequestOAuth2AccessTokenResponseClient 类型 bean,Spring Security 将使用它来配置 OAuth2 客户端组件。

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

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

  • Kotlin

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

}
@Configuration
class SecurityConfig {

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

		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 登录(如果使用此功能)和使用 Spring Security DSL 的 OAuth2 客户端组件。为了理解幕后配置的内容,以下是配置可能的样子

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

  • Kotlin

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

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

@Configuration
@EnableWebSecurity
class SecurityConfig {

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

		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() {
		RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
				new RestClientClientCredentialsTokenResponseClient();
		accessTokenResponseClient.addParametersConverter(parametersConverter());

		return accessTokenResponseClient;
	}

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

}
@Configuration
class SecurityConfig {

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

		return accessTokenResponseClient
	}

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

}

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

为 OAuth2 客户端自定义 RestClient
  • Java

  • Kotlin

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

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val accessTokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.setRestClient(restClient())

		return accessTokenResponseClient
	}

	@Bean
	fun refreshTokenAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
		val accessTokenResponseClient = RestClientRefreshTokenTokenResponseClient()
		accessTokenResponseClient.setRestClient(restClient())

		return accessTokenResponseClient
	}

	@Bean
	fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
		val accessTokenResponseClient = RestClientClientCredentialsTokenResponseClient()
		accessTokenResponseClient.setRestClient(restClient())

		return accessTokenResponseClient
	}

	@Bean
	fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
		val accessTokenResponseClient = RestClientJwtBearerTokenResponseClient()
		accessTokenResponseClient.setRestClient(restClient())

		return accessTokenResponseClient
	}

	@Bean
	fun tokenExchangeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
		val accessTokenResponseClient = RestClientTokenExchangeTokenResponseClient()
		accessTokenResponseClient.setRestClient(restClient())

		return accessTokenResponseClient
	}

	@Bean
	fun restClient(): RestClient {
		// ...
	}

}

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

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

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

为 OAuth2 客户端自定义 RestClient(6.2 之前)
  • Java

  • Kotlin

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

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

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		val tokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
		tokenResponseClient.setRestClient(restClient())

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

		return http.build()
	}

	@Bean
	fun authorizedClientManager(
		clientRegistrationRepository: ClientRegistrationRepository?,
		authorizedClientRepository: OAuth2AuthorizedClientRepository?
	): OAuth2AuthorizedClientManager {
		val refreshTokenAccessTokenResponseClient = RestClientRefreshTokenTokenResponseClient()
		refreshTokenAccessTokenResponseClient.setRestClient(restClient())

		val clientCredentialsAccessTokenResponseClient = RestClientClientCredentialsTokenResponseClient()
		clientCredentialsAccessTokenResponseClient.setRestClient(restClient())

		val jwtBearerAccessTokenResponseClient = RestClientJwtBearerTokenResponseClient()
		jwtBearerAccessTokenResponseClient.setRestClient(restClient())

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

		val tokenExchangeAccessTokenResponseClient = RestClientTokenExchangeTokenResponseClient()
		tokenExchangeAccessTokenResponseClient.setRestClient(restClient())

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

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

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

		return authorizedClientManager
	}

	@Bean
	fun restClient(): RestClient {
		// ...
	}

}

进一步阅读

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

© . This site is unofficial and not affiliated with VMware.