对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

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

概述

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

OAuth2 登录是一个非常强大的 OAuth2 客户端功能,值得在参考文档中单独列出。 但是,它不作为独立功能存在,需要 OAuth2 客户端才能运行。Spring中文文档

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

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

例如,典型的基于 OAuth2 的微服务体系结构可能由单个面向用户的客户端应用程序、多个提供 REST API 的后端资源服务器以及用于管理用户和身份验证问题的第三方授权服务器组成。 通常,单个应用程序仅代表其中一个角色,并且需要与提供其他角色的一个或多个第三方集成。Spring中文文档

Spring Security 可以处理这些场景以及更多场景。 以下各节介绍了 Spring Security 提供的角色,并包含常见场景的示例。Spring中文文档

OAuth2 登录是一个非常强大的 OAuth2 客户端功能,值得在参考文档中单独列出。 但是,它不作为独立功能存在,需要 OAuth2 客户端才能运行。Spring中文文档

OAuth2 资源服务器

本节包含 OAuth2 资源服务器功能的摘要和示例。 有关完整的参考文档,请参阅 OAuth 2.0 资源服务器Spring中文文档

若要开始,请将依赖项添加到项目中。 使用 Spring Boot 时,请添加以下启动器:spring-security-oauth2-resource-serverSpring中文文档

具有 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 安全性,了解不使用 Spring Boot 时的其他选项。Spring中文文档

请考虑 OAuth2 资源服务器的以下用例:Spring中文文档

使用 OAuth2 访问令牌保护访问

使用 OAuth2 访问令牌保护对 API 的访问是很常见的。 在大多数情况下,Spring Security 只需要最少的配置即可使用 OAuth2 保护应用程序。Spring中文文档

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

JWT 支持

以下示例使用 Spring Boot 配置属性配置 Bean:JwtDecoderSpring中文文档

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

使用 Spring Boot 时,这就是所有必需的。 Spring Boot 提供的默认排列等效于以下内容:Spring中文文档

使用 JWT 配置资源服务器
@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 配置属性配置 Bean:OpaqueTokenIntrospectorSpring中文文档

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中文文档

使用不透明令牌配置资源服务器
@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 资源服务器支持可用于任何类型的令牌,包括自定义 JWT。BearerSpring中文文档

使用 JWT 保护 API 所需要的只是一个 bean,它用于验证签名和解码令牌。 Spring Security 将自动使用提供的 bean 在 .JwtDecoderSecurityFilterChainSpring中文文档

以下示例使用 Spring Boot 配置属性配置 Bean:JwtDecoderSpring中文文档

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

您可以将公钥作为类路径资源(在此示例中调用)提供。my-public-key.pubSpring中文文档

使用 Spring Boot 时,这就是所有必需的。 Spring Boot 提供的默认排列等效于以下内容:Spring中文文档

使用自定义 JWT 配置资源服务器
@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 确实提供了接口以及一个实现,即 .JwtEncoderNimbusJwtEncoderSpring中文文档

本节包含 OAuth2 资源服务器功能的摘要和示例。 有关完整的参考文档,请参阅 OAuth 2.0 资源服务器Spring中文文档

请参阅获取 Spring 安全性,了解不使用 Spring Boot 时的其他选项。Spring中文文档

您可以将公钥作为类路径资源(在此示例中调用)提供。my-public-key.pubSpring中文文档

Spring Security 不提供用于铸造代币的端点。 但是,Spring Security 确实提供了接口以及一个实现,即 .JwtEncoderNimbusJwtEncoderSpring中文文档

OAuth2 客户端

本节包含 OAuth2 客户端功能的摘要和示例。 有关完整的参考文档,请参阅 OAuth 2.0 客户端OAuth 2.0 登录Spring中文文档

若要开始,请将依赖项添加到项目中。 使用 Spring Boot 时,请添加以下启动器:spring-security-oauth2-clientSpring中文文档

具有 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 安全性,了解不使用 Spring Boot 时的其他选项。Spring中文文档

请考虑 OAuth2 客户端的以下用例:Spring中文文档

使用 OAuth2 登录用户

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

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

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

}

除了上述配置之外,应用程序还需要至少一个通过使用 Bean 进行配置。 以下示例使用 Spring Boot 配置属性配置 Bean:ClientRegistrationClientRegistrationRepositoryInMemoryClientRegistrationRepositorySpring中文文档

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中文文档

  1. 登录端点(例如)用于启动登录并执行到第三方授权服务器的重定向。/oauth2/authorization/my-oidc-clientSpring中文文档

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

上述配置中存在作用域表示应使用 OpenID Connect 1.0。 这指示 Spring Security 在请求处理期间使用特定于 OIDC 的组件(例如 )。 如果没有此范围,Spring Security 将改用特定于 OAuth2 的组件(例如 )。openidOidcUserServiceOAuth2UserServiceSpring中文文档

访问受保护的资源

向受 OAuth2 保护的第三方 API 发出请求是 OAuth2 客户端的核心用例。 这是通过授权客户端(由 Spring Security 中的类表示)并通过在出站请求的标头中放置令牌来访问受保护的资源来实现的。OAuth2AuthorizedClientBearerAuthorizationSpring中文文档

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

配置 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中文文档

除了上述配置之外,应用程序还需要至少一个通过使用 Bean 进行配置。 以下示例使用 Spring Boot 配置属性配置 Bean:ClientRegistrationClientRegistrationRepositoryInMemoryClientRegistrationRepositorySpring中文文档

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 提供了用于获取可用于访问受保护资源的访问令牌的实现。OAuth2AuthorizedClientManagerSpring中文文档

Spring Security 会为您注册一个默认 bean,当默认 bean 不存在时。OAuth2AuthorizedClientManagerSpring中文文档

使用 an 的最简单方法是通过 an 拦截请求。 要使用 ,您需要添加依赖项以及响应式客户端实现:OAuth2AuthorizedClientManagerExchangeFilterFunctionWebClientWebClientspring-webfluxSpring中文文档

添加 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>

以下示例使用默认值来配置一个能够访问受保护资源的实例,方法是将令牌放在每个请求的标头中:OAuth2AuthorizedClientManagerWebClientBearerAuthorizationSpring中文文档

配置方式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()
	}

}

此配置可以如以下示例所示使用:WebClientSpring中文文档

用于访问受保护的资源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::class.java)
			.block()!!
	}

	data class Message(val message: String)

}

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

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

本部分将“登录用户与 OAuth2”和“访问受保护的资源”合并到单个配置中。 存在其他高级方案,例如配置一个用于登录,另一个用于访问受保护的资源。 所有这些方案都将使用相同的基本配置。ClientRegistrationSpring中文文档

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

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

}

除了上述配置之外,应用程序还需要至少一个通过使用 Bean 进行配置。 以下示例使用 Spring Boot 配置属性配置 Bean:ClientRegistrationClientRegistrationRepositoryInMemoryClientRegistrationRepositorySpring中文文档

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 登录用户访问受保护的资源)与此示例之间的主要区别在于通过属性配置的内容,该属性将标准作用域与自定义作用域和 .scopeopenidprofilemessage.readmessage.writeSpring中文文档

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

Spring Security 会为您注册一个默认 bean,当默认 bean 不存在时。OAuth2AuthorizedClientManagerSpring中文文档

使用 an 的最简单方法是通过 an 拦截请求。 要使用 ,您需要添加依赖项以及响应式客户端实现:OAuth2AuthorizedClientManagerExchangeFilterFunctionWebClientWebClientspring-webfluxSpring中文文档

添加 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>

以下示例使用默认值来配置一个能够访问受保护资源的实例,方法是将令牌放在每个请求的标头中:OAuth2AuthorizedClientManagerWebClientBearerAuthorizationSpring中文文档

配置方式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()
	}

}

此配置可以如以下示例所示使用:WebClientSpring中文文档

用于访问受保护的资源(当前用户)WebClient
@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")
				.retrieve()
				.toEntityList(Message.class)
				.block();
	}

	public record Message(String message) {
	}

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

	@GetMapping("/messages")
	fun messages(): ResponseEntity<List<Message>> {
		return webClient.get()
			.uri("http://localhost:8090/messages")
			.retrieve()
			.toEntityList(Message::class.java)
			.block()!!
	}

	data class Message(val message: String)

}

前面的示例不同,请注意,我们不需要告诉 Spring Security 我们想要使用的。 这是因为它可以从当前登录的用户派生。clientRegistrationIdSpring中文文档

启用扩展授权类型

一个常见的用例涉及启用和/或配置扩展授权类型。 例如,Spring Security 提供对授权类型的支持,但默认情况下不启用它,因为它不是核心 OAuth 2.0 规范的一部分。jwt-bearerSpring中文文档

在 Spring Security 6.2 及更高版本中,我们可以简单地发布一个或多个 bean,它们将被自动拾取。 以下示例仅启用授权类型:OAuth2AuthorizedClientProviderjwt-bearerSpring中文文档

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

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

}
@Configuration
class SecurityConfig {

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

}

当尚未提供默认值时,Spring Security 将自动发布默认值。OAuth2AuthorizedClientManagerSpring中文文档

任何自定义 Bean 也将被拾取并应用于默认授权类型之后提供的 bean。OAuth2AuthorizedClientProviderOAuth2AuthorizedClientManagerSpring中文文档

为了在 Spring Security 6.2 之前实现上述配置,我们必须自己发布这个 bean,并确保我们也重新启用默认授权类型。 若要了解在后台配置的内容,下面是配置可能如下所示:Spring中文文档

启用授权类型(6.2 之前)jwt-bearer
@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 来启用扩展授权类型的功能还提供了定制现有授权类型的机会,而无需重新定义缺省值。 例如,如果我们想自定义 for the grant 的时钟偏移,我们可以简单地发布一个 bean,如下所示:OAuth2AuthorizedClientProviderclient_credentialsSpring中文文档

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

}

自定义令牌请求参数

获取访问令牌时需要自定义请求参数是相当普遍的。 例如,假设我们要向令牌请求添加一个自定义参数,因为提供程序需要此参数进行授权。audienceauthorization_codeSpring中文文档

在 Spring Security 6.2 及更高版本中,我们可以简单地发布具有通用类型的 bean,Spring Security 将使用它来配置 OAuth2 客户端组件。OAuth2AccessTokenResponseClientOAuth2AuthorizationCodeGrantRequestSpring中文文档

以下示例自定义不带 DSL 的授权的令牌请求参数:authorization_codeSpring中文文档

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

}

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

在 Spring Security 6.2 之前,我们必须确保此自定义适用于使用 Spring Security DSL 的 OAuth2 登录(如果使用此功能)和 OAuth2 客户端组件。 若要了解在后台配置的内容,下面是配置可能如下所示:Spring中文文档

自定义授权代码授予的令牌请求参数(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>> {
		// ...
	}

}

对于其他授权类型,我们可以发布其他 Bean 来覆盖缺省值。 例如,要自定义授予的令牌请求,我们可以发布以下 bean:OAuth2AccessTokenResponseClientclient_credentialsSpring中文文档

自定义客户端凭据授予的令牌请求参数
@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 会自动解析以下通用类型的 Bean:OAuth2AccessTokenResponseClientSpring中文文档

  • OAuth2AuthorizationCodeGrantRequest(见DefaultAuthorizationCodeTokenResponseClient)Spring中文文档

  • OAuth2RefreshTokenGrantRequest(见DefaultRefreshTokenTokenResponseClient)Spring中文文档

  • OAuth2ClientCredentialsGrantRequest(见DefaultClientCredentialsTokenResponseClient)Spring中文文档

  • OAuth2PasswordGrantRequest(见DefaultPasswordTokenResponseClient)Spring中文文档

  • JwtBearerGrantRequest(见DefaultJwtBearerTokenResponseClient)Spring中文文档

发布 Bean 类型将自动启用授权类型,而无需单独配置它OAuth2AccessTokenResponseClient<JwtBearerGrantRequest>jwt-bearerSpring中文文档

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

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

在 Spring Security 6.2 及更高版本中,我们可以简单地发布 bean 类型,Spring Security 将为我们配置和发布一个 bean。OAuth2AccessTokenResponseClientOAuth2AuthorizedClientManagerSpring中文文档

以下示例自定义所有受支持的授权类型:RestOperationsSpring中文文档

针对 OAuth2 客户端进行自定义RestOperations
@Configuration
public class SecurityConfig {

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

		return accessTokenResponseClient;
	}

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

		return accessTokenResponseClient;
	}

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

		return accessTokenResponseClient;
	}

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

		return accessTokenResponseClient;
	}

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

		return accessTokenResponseClient;
	}

	@Bean
	public 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 restTemplate(): RestTemplate {
		// ...
	}

}

当尚未提供默认值时,Spring Security 将自动发布默认值。OAuth2AuthorizedClientManagerSpring中文文档

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

在 Spring Security 6.2 之前,我们必须确保此自定义适用于 OAuth2 登录(如果我们使用此功能)和 OAuth2 客户端组件。 我们必须同时使用 Spring Security DSL(用于授权)并为其他授权类型发布 bean。 若要了解在后台配置的内容,下面是配置可能如下所示:authorization_codeOAuth2AuthorizedClientManagerSpring中文文档

针对 OAuth2 客户端进行自定义(6.2 之前版本)RestOperations
@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);

		OAuth2AuthorizedClientProvider authorizedClientProvider =
			OAuth2AuthorizedClientProviderBuilder.builder()
				.authorizationCode()
				.refreshToken((refreshToken) -> refreshToken
					.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
				)
				.clientCredentials((clientCredentials) -> clientCredentials
					.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
				)
				.password((password) -> password
					.accessTokenResponseClient(passwordAccessTokenResponseClient)
				)
				.provider(jwtBearerAuthorizedClientProvider)
				.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 authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
			.authorizationCode()
			.refreshToken { refreshToken ->
				refreshToken.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
			}
			.clientCredentials { clientCredentials ->
				clientCredentials.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
			}
			.password { password ->
				password.accessTokenResponseClient(passwordAccessTokenResponseClient)
			}
			.provider(jwtBearerAuthorizedClientProvider)
			.build()

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

		return authorizedClientManager
	}

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

}

本节包含 OAuth2 客户端功能的摘要和示例。 有关完整的参考文档,请参阅 OAuth 2.0 客户端OAuth 2.0 登录Spring中文文档

请参阅获取 Spring 安全性,了解不使用 Spring Boot 时的其他选项。Spring中文文档

上述配置中存在作用域表示应使用 OpenID Connect 1.0。 这指示 Spring Security 在请求处理期间使用特定于 OIDC 的组件(例如 )。 如果没有此范围,Spring Security 将改用特定于 OAuth2 的组件(例如 )。openidOidcUserServiceOAuth2UserServiceSpring中文文档

上面的示例未提供登录用户的方法。 您可以使用任何其他登录机制(例如 )。 有关与 的组合示例,请参阅下一节formLogin()oauth2Client()oauth2Login()Spring中文文档

Spring Security 会为您注册一个默认 bean,当默认 bean 不存在时。OAuth2AuthorizedClientManagerSpring中文文档

本部分将“登录用户与 OAuth2”和“访问受保护的资源”合并到单个配置中。 存在其他高级方案,例如配置一个用于登录,另一个用于访问受保护的资源。 所有这些方案都将使用相同的基本配置。ClientRegistrationSpring中文文档

前面的示例(使用 OAuth2 登录用户访问受保护的资源)与此示例之间的主要区别在于通过属性配置的内容,该属性将标准作用域与自定义作用域和 .scopeopenidprofilemessage.readmessage.writeSpring中文文档

Spring Security 会为您注册一个默认 bean,当默认 bean 不存在时。OAuth2AuthorizedClientManagerSpring中文文档

前面的示例不同,请注意,我们不需要告诉 Spring Security 我们想要使用的。 这是因为它可以从当前登录的用户派生。clientRegistrationIdSpring中文文档

任何自定义 Bean 也将被拾取并应用于默认授权类型之后提供的 bean。OAuth2AuthorizedClientProviderOAuth2AuthorizedClientManagerSpring中文文档

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

发布 Bean 类型将自动启用授权类型,而无需单独配置它OAuth2AccessTokenResponseClient<JwtBearerGrantRequest>jwt-bearerSpring中文文档

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

延伸阅读

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