OAuth2

Spring Security 提供全面的 OAuth 2.0 支持。 本节讨论如何将 OAuth 2.0 集成到基于 servlet 的应用程序中。spring-doc.cadn.net.cn

概述

Spring Security 的 OAuth 2.0 支持包括两个主要功能集:spring-doc.cadn.net.cn

OAuth2 登录是一个非常强大的 OAuth2 客户端功能,值得在参考文档中单独使用一节。 但是,它不作为独立功能存在,需要 OAuth2 客户端才能运行。spring-doc.cadn.net.cn

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

OAuth2 中的资源服务器客户端角色通常由一个或多个服务器端应用程序表示。 此外,授权服务器角色可以由一个或多个第三方表示(就像在组织内集中身份管理和/或身份验证时一样)-或者-它可以由应用程序表示(就像 Spring Authorization Server 的情况一样)。spring-doc.cadn.net.cn

例如,典型的基于 OAuth2 的微服务架构可能由一个面向用户的客户端应用程序、多个提供 REST API 的后端资源服务器和一个用于管理用户和身份验证问题的第三方授权服务器组成。 同样常见的情况是,单个应用程序仅代表其中一个角色,并且需要与提供其他角色的一个或多个第三方集成。spring-doc.cadn.net.cn

Spring Security 可以处理这些场景以及更多场景。 以下部分介绍了 Spring Security 提供的角色,并包含常见场景的示例。spring-doc.cadn.net.cn

OAuth2 资源服务器

本节包含 OAuth2 Resource Server 功能的摘要和示例。 请参阅 OAuth 2.0 Resource Server 以获取完整的参考文档。spring-doc.cadn.net.cn

要开始使用,请添加spring-security-oauth2-resource-server依赖项。 使用 Spring Boot 时,添加以下 starter:spring-doc.cadn.net.cn

带有 Spring Boot 的 OAuth2 客户端
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 Securityspring-doc.cadn.net.cn

请考虑以下 OAuth2 Resource Server 的使用案例:spring-doc.cadn.net.cn

使用 OAuth2 访问令牌保护访问权限

使用 OAuth2 访问令牌保护对 API 的访问是很常见的。 在大多数情况下, Spring Security 只需要最少的配置即可使用 OAuth2 保护应用程序。spring-doc.cadn.net.cn

有两种类型的BearerSpring Security 支持的令牌,每个令牌使用不同的组件进行验证:spring-doc.cadn.net.cn

JWT 支持

以下示例将JwtDecoder使用 Spring Boot 配置属性的 bean:spring-doc.cadn.net.cn

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

使用 Spring Boot 时,这就是所需的全部内容。 Spring Boot 提供的默认安排等效于以下内容:spring-doc.cadn.net.cn

使用 JWT 配置 Resource Server
@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")
	}

}

不透明令牌支持

以下示例将OpaqueTokenIntrospector使用 Spring Boot 配置属性的 bean:spring-doc.cadn.net.cn

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 提供的默认安排等效于以下内容:spring-doc.cadn.net.cn

使用不透明令牌配置 Resource Server
@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 保护 Access

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

使用 JWT 保护 API 所需要做的只是JwtDecoderbean,用于验证签名和解码令牌。 Spring Security 将自动使用提供的 bean 在SecurityFilterChain.spring-doc.cadn.net.cn

以下示例将JwtDecoder使用 Spring Boot 配置属性的 bean:spring-doc.cadn.net.cn

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

您可以将公钥作为类路径资源(称为my-public-key.pub在此示例中)。spring-doc.cadn.net.cn

使用 Spring Boot 时,这就是所需的全部内容。 Spring Boot 提供的默认安排等效于以下内容:spring-doc.cadn.net.cn

使用自定义 JWT 配置 Resource Server
@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.spring-doc.cadn.net.cn

OAuth2 客户端

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

要开始使用,请添加spring-security-oauth2-client依赖项。 使用 Spring Boot 时,添加以下 starter:spring-doc.cadn.net.cn

带有 Spring Boot 的 OAuth2 客户端
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 Securityspring-doc.cadn.net.cn

请考虑 OAuth2 客户端的以下用例:spring-doc.cadn.net.cn

使用 OAuth2 登录用户

要求用户通过 OAuth2 登录是很常见的。OpenID Connect 1.0 提供了一个称为id_token,旨在为 OAuth2 客户端提供执行用户身份验证和登录用户的能力。 在某些情况下,OAuth2 可以直接用于登录用户(例如,GitHub 和 Facebook 等不实施 OpenID Connect 的流行社交登录提供商就是这种情况)。spring-doc.cadn.net.cn

以下示例将应用程序配置为充当 OAuth2 客户端,能够使用 OAuth2 或 OpenID Connect 将用户登录:spring-doc.cadn.net.cn

配置 OAuth2 登录
@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豆。 以下示例将InMemoryClientRegistrationRepository使用 Spring Boot 配置属性的 bean:spring-doc.cadn.net.cn

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

通过上述配置,应用程序现在支持两个额外的终端节点:spring-doc.cadn.net.cn

  1. 登录端点(例如/oauth2/authorization/my-oidc-client) 用于启动登录并执行到第三方授权服务器的重定向。spring-doc.cadn.net.cn

  2. 重定向端点(例如/login/oauth2/code/my-oidc-client) 被授权服务器重定向回客户端应用程序,并将包含一个code参数用于获取id_token和/或access_token通过 Access Token 请求。spring-doc.cadn.net.cn

存在openidscope 表示应使用 OpenID Connect 1.0。 这会指示 Spring Security 使用特定于 OIDC 的组件(例如OidcUserService) 进行。 如果没有此范围,Spring Security 将使用特定于 OAuth2 的组件(例如DefaultOAuth2UserService) 代替。spring-doc.cadn.net.cn

访问受保护的资源

向受 OAuth2 保护的第三方 API 发出请求是 OAuth2 客户端的核心用例。 这是通过授权客户端(由OAuth2AuthorizedClient类)并通过将Bearertoken 中Authorization标头。spring-doc.cadn.net.cn

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

配置 OAuth2 客户端
@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().spring-doc.cadn.net.cn

除了上述配置外,应用程序至少需要一个ClientRegistration通过使用ClientRegistrationRepository豆。 以下示例将InMemoryClientRegistrationRepository使用 Spring Boot 配置属性的 bean:spring-doc.cadn.net.cn

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用于获取可用于访问受保护资源的访问令牌。spring-doc.cadn.net.cn

Spring Security 注册一个默认的OAuth2AuthorizedClientManagerbean 的 bean 来代替您。spring-doc.cadn.net.cn

使用OAuth2AuthorizedClientManager是通过ClientHttpRequestInterceptor它通过RestClient,该 API 在spring-web位于 Classpath 上。spring-doc.cadn.net.cn

以下示例使用默认的OAuth2AuthorizedClientManager要配置RestClient能够通过以下方式访问受保护的资源Bearer令牌Authorization标头:spring-doc.cadn.net.cn

配置RestClientClientHttpRequestInterceptor
@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可以像以下示例中一样使用:spring-doc.cadn.net.cn

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

}
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 客户端的核心用例。 这是通过授权客户端(由OAuth2AuthorizedClient类)并通过将Bearertoken 中Authorization标头。spring-doc.cadn.net.cn

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

配置 OAuth2 客户端
@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().spring-doc.cadn.net.cn

除了上述配置外,应用程序至少需要一个ClientRegistration通过使用ClientRegistrationRepository豆。 以下示例将InMemoryClientRegistrationRepository使用 Spring Boot 配置属性的 bean:spring-doc.cadn.net.cn

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用于获取可用于访问受保护资源的访问令牌。spring-doc.cadn.net.cn

Spring Security 注册一个默认的OAuth2AuthorizedClientManagerbean 的 bean 来代替您。spring-doc.cadn.net.cn

而不是配置RestClient,另一种使用OAuth2AuthorizedClientManager是通过ExchangeFilterFunction它通过WebClient. 要使用WebClient,您需要添加spring-webfluxdependency 以及响应式客户端实现:spring-doc.cadn.net.cn

添加 Spring WebFlux 依赖项
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能够通过以下方式访问受保护的资源Bearer令牌Authorization标头:spring-doc.cadn.net.cn

配置WebClientExchangeFilterFunction
@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可以像以下示例中一样使用:spring-doc.cadn.net.cn

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

}
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同时为两个使用案例进行配置。spring-doc.cadn.net.cn

本部分将 Log Users In with OAuth2Access Protected Resources 合并到一个配置中。 存在其他高级方案,例如配置一个ClientRegistration用于登录,另一个用于访问受保护的资源。 所有此类场景都将使用相同的基本配置。spring-doc.cadn.net.cn

以下示例将应用程序配置为 OAuth2 客户端,该客户端能够使用户登录并从第三方 API 请求受保护的资源:spring-doc.cadn.net.cn

配置 OAuth2 登录和 OAuth2 客户端
@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豆。 以下示例将InMemoryClientRegistrationRepository使用 Spring Boot 配置属性的 bean:spring-doc.cadn.net.cn

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-doc.cadn.net.cn

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

Spring Security 注册一个默认的OAuth2AuthorizedClientManagerbean 的 bean 来代替您。spring-doc.cadn.net.cn

使用OAuth2AuthorizedClientManager是通过ClientHttpRequestInterceptor它通过RestClient,该 API 在spring-web位于 Classpath 上。spring-doc.cadn.net.cn

以下示例使用默认的OAuth2AuthorizedClientManager要配置RestClient能够通过以下方式访问受保护的资源Bearer令牌Authorization标头:spring-doc.cadn.net.cn

配置RestClientClientHttpRequestInterceptor
@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可以像以下示例中一样使用:spring-doc.cadn.net.cn

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

}
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我们想用。 这是因为它可以从当前登录的用户派生。spring-doc.cadn.net.cn

使用客户端凭证授予

本节重点介绍客户端凭证授权类型的其他注意事项。 请参阅访问受保护的资源,了解所有授权类型的常规设置和使用。spring-doc.cadn.net.cn

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

请务必注意,客户端凭据授予的典型使用意味着任何请求(或用户)都可能获取访问令牌并向资源服务器发出受保护的资源请求。 在设计应用程序时要小心,以确保用户无法发出未经授权的请求,因为每个请求都能够获得访问令牌。spring-doc.cadn.net.cn

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

默认情况下,访问令牌的范围限定为当前用户的主体名称,这意味着每个用户都将收到唯一的访问令牌。spring-doc.cadn.net.cn

使用客户端凭据授予的客户端通常需要将访问令牌的范围限定为应用程序,而不是单个用户,因此每个应用程序只有一个访问令牌。 为了将访问令牌的范围限定为应用程序,您需要设置解析自定义主体名称的策略。 以下示例通过配置RestClient使用RequestAttributePrincipalResolver:spring-doc.cadn.net.cn

配置RestClientclient_credentials
@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()
	}

}

完成上述配置后,可以为每个请求指定一个委托人名称。 以下示例演示如何通过指定主体名称将访问令牌的范围限定为应用程序:spring-doc.cadn.net.cn

将访问令牌的范围限定为应用程序
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)

}

如上例所示,通过 attributes 指定主体名称时,将只有一个访问令牌,它将用于所有请求。spring-doc.cadn.net.cn

启用扩展授权类型

一个常见的使用案例涉及启用和/或配置扩展授权类型。 例如,Spring Security 为jwt-bearertoken-exchangegrant 类型,但默认情况下不会启用它们,因为它们不是核心 OAuth 2.0 规范的一部分。spring-doc.cadn.net.cn

使用 Spring Security 6.2 及更高版本,我们可以简单地为一个或多个发布一个 beanOAuth2AuthorizedClientProvider他们将自动被选取。 以下示例仅启用jwt-bearer资助类型:spring-doc.cadn.net.cn

使jwt-bearer授权类型
@Configuration
public class SecurityConfig {

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

}
@Configuration
class SecurityConfig {

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

}

默认的OAuth2AuthorizedClientManager将由 Spring Security 在尚未提供时自动发布。spring-doc.cadn.net.cn

任何自定义OAuth2AuthorizedClientProviderbean 也会被选取并应用于提供的OAuth2AuthorizedClientManager在默认授权类型之后。spring-doc.cadn.net.cn

为了在 Spring Security 6.2 之前实现上述配置,我们必须自己发布这个 bean,并确保我们也重新启用了默认授权类型。 要了解幕后配置的内容,以下是配置可能的样子:spring-doc.cadn.net.cn

使jwt-bearer授权类型(6.2 之前的版本)
@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_credentialsgrant,我们可以简单地发布一个 bean,如下所示:spring-doc.cadn.net.cn

自定义客户端凭据授权类型
@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
	}

}

自定义 Token 请求参数

在获取访问令牌时,需要自定义请求参数是相当普遍的。 例如,假设我们想添加一个自定义audience参数添加到令牌请求中,因为提供程序需要此参数来执行authorization_code授予。spring-doc.cadn.net.cn

使用 Spring Security 6.2 及更高版本,我们可以简单地发布OAuth2AccessTokenResponseClient替换为泛型类型OAuth2AuthorizationCodeGrantRequestSpring Security 将使用它来配置 OAuth2 客户端组件。spring-doc.cadn.net.cn

以下示例自定义authorization_codegrant 来获得 DSL 的 API 请求:spring-doc.cadn.net.cn

自定义授权码授予的 Token 请求参数
@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"
			}
		}
	}

}

Notice that we don’t need to customize the SecurityFilterChain bean in this case, and can stick with the defaults. If using Spring Boot with no additional customizations, we can actually omit the SecurityFilterChain bean entirely.spring-doc.cadn.net.cn

Prior to Spring Security 6.2, we had to ensure that this customization was applied for both OAuth2 Login (if we are using this feature) and OAuth2 Client components using the Spring Security DSL. To understand what is being configured behind the scenes, here’s what the configuration might have looked like:spring-doc.cadn.net.cn

Customize Token Request Parameters for Authorization Code Grant (prior to 6.2)
@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>> {
		// ...
	}

}

For other grant types we can publish additional OAuth2AccessTokenResponseClient beans to override the defaults. For example, to customize token requests for the client_credentials grant we can publish the following bean:spring-doc.cadn.net.cn

Customize Token Request Parameters for Client Credentials Grant
@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 automatically resolves the following generic types of OAuth2AccessTokenResponseClient beans:spring-doc.cadn.net.cn

Publishing a bean of type OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> will automatically enable the jwt-bearer grant type without the need to configure it separately.spring-doc.cadn.net.cn

Publishing a bean of type OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> will automatically enable the token-exchange grant type without the need to configure it separately.spring-doc.cadn.net.cn

Customize the RestOperations used by OAuth2 Client Components

Another common use case is the need to customize the RestOperations used when obtaining an access token. We might need to do this to customize processing of the response (via a custom HttpMessageConverter) or to apply proxy settings for a corporate network (via a customized ClientHttpRequestFactory).spring-doc.cadn.net.cn

With Spring Security 6.2 and later, we can simply publish beans of type OAuth2AccessTokenResponseClient and Spring Security will configure and publish an OAuth2AuthorizedClientManager bean for us.spring-doc.cadn.net.cn

The following example customizes the RestOperations for all of the supported grant types:spring-doc.cadn.net.cn

Customize RestOperations for OAuth2 Client
@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将由 Spring Security 在尚未提供时自动发布。spring-doc.cadn.net.cn

Notice that we don’t need to customize the SecurityFilterChain bean in this case, and can stick with the defaults. If using Spring Boot with no additional customizations, we can actually omit the SecurityFilterChain bean entirely.spring-doc.cadn.net.cn

Prior to Spring Security 6.2, we had to ensure this customization was applied to both OAuth2 Login (if we are using this feature) and OAuth2 Client components. We had to use both the Spring Security DSL (for the authorization_code grant) and publish a bean of type OAuth2AuthorizedClientManager for other grant types. To understand what is being configured behind the scenes, here’s what the configuration might have looked like:spring-doc.cadn.net.cn

Customize RestOperations for OAuth2 Client (prior to 6.2)
@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 {
		// ...
	}

}

Further Reading

The preceding sections introduced Spring Security’s support for OAuth2 with examples for common scenarios. You can read more about OAuth2 Client and Resource Server in the following sections of the reference documentation:spring-doc.cadn.net.cn