OAuth2 WebFlux

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

概述

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

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

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

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

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

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

OAuth2 资源服务器

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

首先,将依赖项添加到您的项目中。 使用 Spring Boot 时,添加以下 starter:spring-security-oauth2-resource-serverspring-doc.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.cn

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

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

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

Spring Security 支持两种类型的令牌,每种令牌都使用不同的组件进行验证:Bearerspring-doc.cn

JWT 支持

下面的示例使用 Spring Boot 配置属性配置 Bean:ReactiveJwtDecoderspring-doc.cn

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

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

使用 JWT 配置 Resource Server
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			.authorizeExchange((authorize) -> authorize
				.anyExchange().authenticated()
			)
			.oauth2ResourceServer((oauth2) -> oauth2
				.jwt(Customizer.withDefaults())
			);
		return http.build();
	}

	@Bean
	public ReactiveJwtDecoder jwtDecoder() {
		return ReactiveJwtDecoders.fromIssuerLocation("https://my-auth-server.com");
	}

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

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			authorizeExchange {
				authorize(anyExchange, authenticated)
			}
			oauth2ResourceServer {
				jwt { }
			}
		}
	}

	@Bean
	fun jwtDecoder(): ReactiveJwtDecoder {
		return ReactiveJwtDecoders.fromIssuerLocation("https://my-auth-server.com")
	}

}

不透明令牌支持

以下示例使用 Spring Boot 配置属性配置 Bean:ReactiveOpaqueTokenIntrospectorspring-doc.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.cn

使用不透明令牌配置 Resource Server
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			.authorizeExchange((authorize) -> authorize
				.anyExchange().authenticated()
			)
			.oauth2ResourceServer((oauth2) -> oauth2
				.opaqueToken(Customizer.withDefaults())
			);
		return http.build();
	}

	@Bean
	public ReactiveOpaqueTokenIntrospector opaqueTokenIntrospector() {
		return new SpringReactiveOpaqueTokenIntrospector(
			"https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret");
	}

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

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			authorizeExchange {
				authorize(anyExchange, authenticated)
			}
			oauth2ResourceServer {
				opaqueToken { }
			}
		}
	}

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

}

使用自定义 JWT 保护 Access

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

使用 JWT 保护 API 所需的只是一个 Bean,它用于验证签名和解码令牌。 Spring Security 将自动使用提供的 bean 在 .ReactiveJwtDecoderSecurityWebFilterChainspring-doc.cn

下面的示例使用 Spring Boot 配置属性配置 Bean:ReactiveJwtDecoderspring-doc.cn

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

您可以将公钥作为 Classpath 资源(在本例中称为)提供。my-public-key.pubspring-doc.cn

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

使用自定义 JWT 配置 Resource Server
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			.authorizeExchange((authorize) -> authorize
				.anyExchange().authenticated()
			)
			.oauth2ResourceServer((oauth2) -> oauth2
				.jwt(Customizer.withDefaults())
			);
		return http.build();
	}

	@Bean
	public ReactiveJwtDecoder jwtDecoder() {
		return NimbusReactiveJwtDecoder.withPublicKey(publicKey()).build();
	}

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

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

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			authorizeExchange {
				authorize(anyExchange, authenticated)
			}
			oauth2ResourceServer {
				jwt { }
			}
		}
	}

	@Bean
	fun jwtDecoder(): ReactiveJwtDecoder {
		return NimbusReactiveJwtDecoder.withPublicKey(publicKey()).build()
	}

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

}

Spring Security 不提供用于铸造代币的端点。 但是,Spring Security 确实提供了接口以及一个实现,即 .JwtEncoderNimbusJwtEncoderspring-doc.cn

OAuth2 客户端

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

首先,将依赖项添加到您的项目中。 使用 Spring Boot 时,添加以下 starter:spring-security-oauth2-clientspring-doc.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.cn

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

使用 OAuth2 登录用户

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

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

配置 OAuth2 登录
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			// ...
			.oauth2Login(Customizer.withDefaults());
		return http.build();
	}

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

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			// ...
			oauth2Login { }
		}
	}

}

除了上述配置之外,应用程序还要求至少使用 bean 配置一个。 以下示例使用 Spring Boot 配置属性配置 Bean:ClientRegistrationReactiveClientRegistrationRepositoryInMemoryReactiveClientRegistrationRepositoryspring-doc.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.cn

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

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

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

访问受保护的资源

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

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

配置 OAuth2 客户端
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			// ...
			.oauth2Client(Customizer.withDefaults());
		return http.build();
	}

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

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			// ...
			oauth2Client { }
		}
	}

}

上面的示例没有提供登录用户的方法。 您可以使用任何其他登录机制(如 )。 有关与 的组合示例,请参阅下一节formLogin()oauth2Client()oauth2Login()spring-doc.cn

除了上述配置之外,应用程序还要求至少使用 bean 配置一个。 以下示例使用 Spring Boot 配置属性配置 Bean:ClientRegistrationReactiveClientRegistrationRepositoryInMemoryReactiveClientRegistrationRepositoryspring-doc.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 提供了用于获取可用于访问受保护资源的访问令牌的实现。ReactiveOAuth2AuthorizedClientManagerspring-doc.cn

Spring Security 在不存在默认 bean 时为您注册一个默认 bean。ReactiveOAuth2AuthorizedClientManagerspring-doc.cn

使用 a 的最简单方法是通过 an 通过 .ReactiveOAuth2AuthorizedClientManagerExchangeFilterFunctionWebClientspring-doc.cn

以下示例使用默认值来配置能够访问受保护资源的功能,方法是在每个请求的标头中放置令牌:ReactiveOAuth2AuthorizedClientManagerWebClientBearerAuthorizationspring-doc.cn

配置WebClientExchangeFilterFunction
@Configuration
public class WebClientConfig {

	@Bean
	public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
		ServerOAuth2AuthorizedClientExchangeFilterFunction filter =
				new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
		return WebClient.builder()
				.filter(filter)
				.build();
	}

}
@Configuration
class WebClientConfig {

	@Bean
	fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
		val filter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
		return WebClient.builder()
			.filter(filter)
			.build()
	}

}

此配置可用于以下示例:WebClientspring-doc.cn

用于访问受保护的资源WebClient
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;

@RestController
public class MessagesController {

	private final WebClient webClient;

	public MessagesController(WebClient webClient) {
		this.webClient = webClient;
	}

	@GetMapping("/messages")
	public Mono<ResponseEntity<List<Message>>> messages() {
		return this.webClient.get()
				.uri("http://localhost:8090/messages")
				.attributes(clientRegistrationId("my-oauth2-client"))
				.retrieve()
				.toEntityList(Message.class);
	}

	public record Message(String message) {
	}

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

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

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

	data class Message(val message: String)

}

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

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

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

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

配置 OAuth2 登录和 OAuth2 客户端
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			// ...
			.oauth2Login(Customizer.withDefaults())
			.oauth2Client(Customizer.withDefaults());
		return http.build();
	}

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

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			// ...
			oauth2Login { }
			oauth2Client { }
		}
	}

}

除了上述配置之外,应用程序还要求至少使用 bean 配置一个。 以下示例使用 Spring Boot 配置属性配置 Bean:ClientRegistrationReactiveClientRegistrationRepositoryInMemoryReactiveClientRegistrationRepositoryspring-doc.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 登录用户、访问受保护的资源)与此示例之间的主要区别在于通过属性配置的内容,该属性结合了标准范围和自定义范围 和 。scopeopenidprofilemessage.readmessage.writespring-doc.cn

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

Spring Security 在不存在默认 bean 时为您注册一个默认 bean。ReactiveOAuth2AuthorizedClientManagerspring-doc.cn

使用 a 的最简单方法是通过 an 通过 .ReactiveOAuth2AuthorizedClientManagerExchangeFilterFunctionWebClientspring-doc.cn

以下示例使用默认值来配置能够访问受保护资源的功能,方法是在每个请求的标头中放置令牌:ReactiveOAuth2AuthorizedClientManagerWebClientBearerAuthorizationspring-doc.cn

配置WebClientExchangeFilterFunction
@Configuration
public class WebClientConfig {

	@Bean
	public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
		ServerOAuth2AuthorizedClientExchangeFilterFunction filter =
				new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
		return WebClient.builder()
				.filter(filter)
				.build();
	}

}
@Configuration
class WebClientConfig {

	@Bean
	fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
		val filter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
		return WebClient.builder()
			.filter(filter)
			.build()
	}

}

此配置可用于以下示例:WebClientspring-doc.cn

用于访问受保护的资源(当前用户)WebClient
@RestController
public class MessagesController {

	private final WebClient webClient;

	public MessagesController(WebClient webClient) {
		this.webClient = webClient;
	}

	@GetMapping("/messages")
	public Mono<ResponseEntity<List<Message>>> messages() {
		return this.webClient.get()
				.uri("http://localhost:8090/messages")
				.retrieve()
				.toEntityList(Message.class);
	}

	public record Message(String message) {
	}

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

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

	data class Message(val message: String)

}

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

启用扩展授权类型

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

使用 Spring Security 6.3 及更高版本,我们可以简单地为一个或多个发布一个 bean,它们将被自动选取。 以下示例仅启用 grant 类型:ReactiveOAuth2AuthorizedClientProviderjwt-bearerspring-doc.cn

Enable Grant Typejwt-bearer
@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AuthorizedClientProvider jwtBearer() {
		return new JwtBearerReactiveOAuth2AuthorizedClientProvider();
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun jwtBearer(): ReactiveOAuth2AuthorizedClientProvider {
		return JwtBearerReactiveOAuth2AuthorizedClientProvider()
	}

}

如果尚未提供默认值,则 Spring Security 将自动发布默认值。ReactiveOAuth2AuthorizedClientManagerspring-doc.cn

任何自定义 bean 也将被选取并应用于默认授权类型之后提供的 bean。OAuth2AuthorizedClientProviderReactiveOAuth2AuthorizedClientManagerspring-doc.cn

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

启用 Grant Type(6.3 之前的版本)jwt-bearer
@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
			ReactiveClientRegistrationRepository clientRegistrationRepository,
			ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

		ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
			ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
				.authorizationCode()
				.refreshToken()
				.clientCredentials()
				.password()
				.provider(new JwtBearerReactiveOAuth2AuthorizedClientProvider())
				.build();

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

		return authorizedClientManager;
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizedClientManager(
		clientRegistrationRepository: ReactiveClientRegistrationRepository,
		authorizedClientRepository: ServerOAuth2AuthorizedClientRepository
	): ReactiveOAuth2AuthorizedClientManager {
		val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
			.authorizationCode()
			.refreshToken()
			.clientCredentials()
			.password()
			.provider(JwtBearerReactiveOAuth2AuthorizedClientProvider())
			.build()

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

		return authorizedClientManager
	}

}

自定义现有授权类型

通过发布 bean 来启用扩展授权类型的功能还提供了自定义现有授权类型的机会,而无需重新定义默认值。 例如,如果我们想自定义 for the grant 的 clock skew,我们可以简单地发布一个 bean,如下所示:ReactiveOAuth2AuthorizedClientProviderclient_credentialsspring-doc.cn

自定义客户端凭据授权类型
@Configuration
public class SecurityConfig {

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

		return authorizedClientProvider;
	}

}
@Configuration
class SecurityConfig {

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

}

自定义 Token 请求参数

在获取访问令牌时,需要自定义请求参数是相当普遍的。 例如,假设我们想向令牌请求添加自定义参数,因为提供商需要此参数进行授权。audienceauthorization_codespring-doc.cn

我们可以简单地发布一个泛型类型的 bean,Spring Security 将使用它来配置 OAuth2 客户端组件。ReactiveOAuth2AccessTokenResponseClientOAuth2AuthorizationCodeGrantRequestspring-doc.cn

以下示例自定义授权的 token 请求参数:authorization_codespring-doc.cn

自定义授权码授予的 Token 请求参数
@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
		WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveAuthorizationCodeTokenResponseClient();
		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(): ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
		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"
			}
		}
	}

}

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

如您所见,提供 as a bean 非常方便。 当直接使用 Spring Security DSL 时,我们需要确保此自定义同时应用于 OAuth2 登录(如果我们使用此功能)和 OAuth2 客户端组件。 要了解幕后配置的内容,以下是 DSL 的配置情况:ReactiveOAuth2AccessTokenResponseClientspring-doc.cn

使用 DSL 自定义授权码授予的 Token 请求参数
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.addParametersConverter(parametersConverter());

		http
			.authorizeExchange((authorize) -> authorize
				.anyExchange().authenticated()
			)
			.oauth2Login((oauth2Login) -> oauth2Login
				.authenticationManager(new DelegatingReactiveAuthenticationManager(
					new OidcAuthorizationCodeReactiveAuthenticationManager(
						accessTokenResponseClient, new OidcReactiveOAuth2UserService()
					),
					new OAuth2LoginReactiveAuthenticationManager(
						accessTokenResponseClient, new DefaultReactiveOAuth2UserService()
					)
				))
			)
			.oauth2Client((oauth2Client) -> oauth2Client
				.authenticationManager(new OAuth2AuthorizationCodeReactiveAuthenticationManager(
					accessTokenResponseClient
				))
			);

		return http.build();
	}

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

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

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.addParametersConverter(parametersConverter())

		return http {
			authorizeExchange {
				authorize(anyExchange, authenticated)
			}
			oauth2Login {
				authenticationManager = DelegatingReactiveAuthenticationManager(
					OidcAuthorizationCodeReactiveAuthenticationManager(
						accessTokenResponseClient, OidcReactiveOAuth2UserService()
					),
					OAuth2LoginReactiveAuthenticationManager(
						accessTokenResponseClient, DefaultReactiveOAuth2UserService()
					)
				)
			}
			oauth2Client {
				authenticationManager = OAuth2AuthorizationCodeReactiveAuthenticationManager(
					accessTokenResponseClient
				)
			}
		}
	}

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

}

对于其他授权类型,我们可以发布其他 bean 来覆盖默认值。 例如,要自定义授权的令牌请求,我们可以发布以下 bean:ReactiveOAuth2AccessTokenResponseClientclient_credentialsspring-doc.cn

自定义客户端凭证授予的令牌请求参数
@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
		WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
				new WebClientReactiveClientCredentialsTokenResponseClient();
		accessTokenResponseClient.addParametersConverter(parametersConverter());

		return accessTokenResponseClient;
	}

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

}
@Configuration
class SecurityConfig {

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

		return accessTokenResponseClient
	}

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

}

Spring Security 会自动解析以下泛型 bean:ReactiveOAuth2AccessTokenResponseClientspring-doc.cn

  • OAuth2AuthorizationCodeGrantRequest(参见WebClientReactiveAuthorizationCodeTokenResponseClient)spring-doc.cn

  • OAuth2RefreshTokenGrantRequest(参见WebClientReactiveRefreshTokenTokenResponseClient)spring-doc.cn

  • OAuth2ClientCredentialsGrantRequest(参见WebClientReactiveClientCredentialsTokenResponseClient)spring-doc.cn

  • OAuth2PasswordGrantRequest(参见WebClientReactivePasswordTokenResponseClient)spring-doc.cn

  • JwtBearerGrantRequest(参见WebClientReactiveJwtBearerTokenResponseClient)spring-doc.cn

  • TokenExchangeGrantRequest(参见WebClientReactiveTokenExchangeTokenResponseClient)spring-doc.cn

发布 类型的 bean 将自动启用 grant 类型,而无需单独配置它ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest>jwt-bearerspring-doc.cn

发布 类型的 bean 将自动启用 grant 类型,而无需单独配置它ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest>token-exchangespring-doc.cn

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

另一个常见的用例是在获取访问令牌时需要自定义 used。 我们可能需要执行此操作来自定义基础 HTTP 客户端库(通过自定义)以配置 SSL 设置或为公司网络应用代理设置。WebClientClientHttpConnectorspring-doc.cn

使用 Spring Security 6.3 及更高版本,我们只需发布 bean 类型,Spring Security 将为我们配置和发布 bean。ReactiveOAuth2AccessTokenResponseClientReactiveOAuth2AuthorizedClientManagerspring-doc.cn

以下示例为所有受支持的授权类型自定义 :WebClientspring-doc.cn

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

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
		WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
		WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveRefreshTokenTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
		WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveClientCredentialsTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
		WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
			new WebClientReactivePasswordTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
		WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveJwtBearerTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
		WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveTokenExchangeTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public WebClient webClient() {
		// ...
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizationCodeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun refreshTokenAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun clientCredentialsAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun passwordAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
		val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun jwtBearerAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun tokenExchangeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun webClient(): WebClient {
		// ...
	}

}

如果尚未提供默认值,则 Spring Security 将自动发布默认值。ReactiveOAuth2AuthorizedClientManagerspring-doc.cn

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

在 Spring Security 6.3 之前,我们必须确保自己将此自定义应用于 OAuth2 客户端组件。 虽然我们可以为授权发布 type 的 bean,但我们必须为其他授权类型发布 type 的 bean。 要了解幕后配置的内容,以下是配置可能的样子:ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>authorization_codeReactiveOAuth2AuthorizedClientManagerspring-doc.cn

针对 OAuth2 客户端进行自定义(6.3 之前版本)WebClient
@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
		WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
			ReactiveClientRegistrationRepository clientRegistrationRepository,
			ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

		WebClientReactiveRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient =
			new WebClientReactiveRefreshTokenTokenResponseClient();
		refreshTokenAccessTokenResponseClient.setWebClient(webClient());

		WebClientReactiveClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient =
			new WebClientReactiveClientCredentialsTokenResponseClient();
		clientCredentialsAccessTokenResponseClient.setWebClient(webClient());

		WebClientReactivePasswordTokenResponseClient passwordAccessTokenResponseClient =
			new WebClientReactivePasswordTokenResponseClient();
		passwordAccessTokenResponseClient.setWebClient(webClient());

		WebClientReactiveJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
			new WebClientReactiveJwtBearerTokenResponseClient();
		jwtBearerAccessTokenResponseClient.setWebClient(webClient());

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

		WebClientReactiveTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient =
			new WebClientReactiveTokenExchangeTokenResponseClient();
		tokenExchangeAccessTokenResponseClient.setWebClient(webClient());

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

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

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

		return authorizedClientManager;
	}

	@Bean
	public WebClient webClient() {
		// ...
	}

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

@Configuration
class SecurityConfig {

	@Bean
	fun authorizationCodeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun authorizedClientManager(
		clientRegistrationRepository: ReactiveClientRegistrationRepository?,
		authorizedClientRepository: ServerOAuth2AuthorizedClientRepository?
	): ReactiveOAuth2AuthorizedClientManager {
		val refreshTokenAccessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
		refreshTokenAccessTokenResponseClient.setWebClient(webClient())

		val clientCredentialsAccessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
		clientCredentialsAccessTokenResponseClient.setWebClient(webClient())

		val passwordAccessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
		passwordAccessTokenResponseClient.setWebClient(webClient())

		val jwtBearerAccessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
		jwtBearerAccessTokenResponseClient.setWebClient(webClient())

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

		val tokenExchangeAccessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
		tokenExchangeAccessTokenResponseClient.setWebClient(webClient())

		val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider()
		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 = DefaultReactiveOAuth2AuthorizedClientManager(
			clientRegistrationRepository, authorizedClientRepository
		)
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

		return authorizedClientManager
	}

	@Bean
	fun webClient(): WebClient {
		// ...
	}

}

延伸阅读

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