此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.5spring-doc.cadn.net.cn

OAuth2 WebFlux

Spring Security 提供全面的 OAuth 2.0 支持。 本节讨论如何将 OAuth 2.0 集成到响应式应用程序中。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 支持

以下示例将ReactiveJwtDecoder使用 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
@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")
	}

}

Opaque Token Support

The following example configures an OpaqueTokenIntrospector bean using Spring Boot configuration properties: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

When using Spring Boot, this is all that is required. The default arrangement provided by Spring Boot is equivalent to the following:spring-doc.cadn.net.cn

Configure Resource Server with Opaque Tokens
@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"
		)
	}

}

Protect Access with a custom JWT

It is a fairly common goal to protect access to an API using JWTs, particularly when the frontend is developed as a single-page application. The OAuth2 Resource Server support in Spring Security can be used for any type of Bearer token, including a custom JWT.spring-doc.cadn.net.cn

All that is required to protect an API using JWTs is a ReactiveJwtDecoder bean, which is used to validate signatures and decode tokens. Spring Security will automatically use the provided bean to configure protection within the SecurityWebFilterChain.spring-doc.cadn.net.cn

The following example configures a ReactiveJwtDecoder bean using Spring Boot configuration properties:spring-doc.cadn.net.cn

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

You can provide the public key as a classpath resource (called my-public-key.pub in this example).spring-doc.cadn.net.cn

When using Spring Boot, this is all that is required. The default arrangement provided by Spring Boot is equivalent to the following:spring-doc.cadn.net.cn

Configure Resource Server with Custom JWTs
@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 does not provide an endpoint for minting tokens. However, Spring Security does provide the JwtEncoder interface along with one implementation, which is NimbusJwtEncoder.spring-doc.cadn.net.cn

OAuth2 Client

This section contains a summary of OAuth2 Client features with examples. See OAuth 2.0 Client and OAuth 2.0 Login for complete reference documentation.spring-doc.cadn.net.cn

To get started, add the spring-security-oauth2-client dependency to your project. When using Spring Boot, add the following starter:spring-doc.cadn.net.cn

OAuth2 Client with Spring Boot
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

See Getting Spring Security for additional options when not using Spring Boot.spring-doc.cadn.net.cn

Consider the following use cases for OAuth2 Client:spring-doc.cadn.net.cn

Log Users In with OAuth2

It is very common to require users to log in via OAuth2. OpenID Connect 1.0 provides a special token called the id_token which is designed to provide an OAuth2 Client with the ability to perform user identity verification and log users in. In certain cases, OAuth2 can be used directly to log users in (as is the case with popular social login providers that do not implement OpenID Connect such as GitHub and Facebook).spring-doc.cadn.net.cn

The following example configures the application to act as an OAuth2 Client capable of logging users in with OAuth2 or OpenID Connect:spring-doc.cadn.net.cn

Configure OAuth2 Login
@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 { }
		}
	}

}

In addition to the above configuration, the application requires at least one ClientRegistration to be configured through the use of a ReactiveClientRegistrationRepository bean. The following example configures an InMemoryReactiveClientRegistrationRepository bean using Spring Boot configuration properties: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

With the above configuration, the application now supports two additional endpoints:spring-doc.cadn.net.cn

  1. The login endpoint (e.g. /oauth2/authorization/my-oidc-client) is used to initiate login and perform a redirect to the third party authorization server.spring-doc.cadn.net.cn

  2. The redirection endpoint (e.g. /login/oauth2/code/my-oidc-client) is used by the authorization server to redirect back to the client application, and will contain a code parameter used to obtain an id_token and/or access_token via the access token request.spring-doc.cadn.net.cn

The presence of the openid scope in the above configuration indicates that OpenID Connect 1.0 should be used. This instructs Spring Security to use OIDC-specific components (such as OidcReactiveOAuth2UserService) during request processing. Without this scope, Spring Security will use OAuth2-specific components (such as DefaultReactiveOAuth2UserService) instead.spring-doc.cadn.net.cn

Access Protected Resources

Making requests to a third party API that is protected by OAuth2 is a core use case of OAuth2 Client. This is accomplished by authorizing a client (represented by the OAuth2AuthorizedClient class in Spring Security) and accessing protected resources by placing a Bearer token in the Authorization header of an outbound request.spring-doc.cadn.net.cn

The following example configures the application to act as an OAuth2 Client capable of requesting protected resources from a third party API:spring-doc.cadn.net.cn

Configure OAuth2 Client
@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 { }
		}
	}

}

The above example does not provide a way to log users in. You can use any other login mechanism (such as formLogin()). See the next section for an example combining oauth2Client() with oauth2Login().spring-doc.cadn.net.cn

In addition to the above configuration, the application requires at least one ClientRegistration to be configured through the use of a ReactiveClientRegistrationRepository bean. The following example configures an InMemoryReactiveClientRegistrationRepository bean using Spring Boot configuration properties: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

In addition to configuring Spring Security to support OAuth2 Client features, you will also need to decide how you will be accessing protected resources and configure your application accordingly. Spring Security provides implementations of ReactiveOAuth2AuthorizedClientManager for obtaining access tokens that can be used to access protected resources.spring-doc.cadn.net.cn

Spring Security registers a default ReactiveOAuth2AuthorizedClientManager bean for you when one does not exist.spring-doc.cadn.net.cn

The easiest way to use a ReactiveOAuth2AuthorizedClientManager is via an ExchangeFilterFunction that intercepts requests through a WebClient.spring-doc.cadn.net.cn

The following example uses the default ReactiveOAuth2AuthorizedClientManager to configure a WebClient capable of accessing protected resources by placing Bearer tokens in the Authorization header of each request:spring-doc.cadn.net.cn

Configure WebClient with ExchangeFilterFunction
@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()
	}

}

This configured WebClient can be used as in the following example:spring-doc.cadn.net.cn

Use WebClient to Access Protected Resources
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)

}

Access Protected Resources for the Current User

When a user is logged in via OAuth2 or OpenID Connect, the authorization server may provide an access token that can be used directly to access protected resources. This is convenient because it only requires a single ClientRegistration to be configured for both use cases simultaneously.spring-doc.cadn.net.cn

This section combines Log Users In with OAuth2 and Access Protected Resources into a single configuration. Other advanced scenarios exist, such as configuring one ClientRegistration for login and another for accessing protected resources. All such scenarios would use the same basic configuration.spring-doc.cadn.net.cn

The following example configures the application to act as an OAuth2 Client capable of logging the user in and requesting protected resources from a third party API:spring-doc.cadn.net.cn

Configure OAuth2 Login and OAuth2 Client
@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 { }
		}
	}

}

In addition to the above configuration, the application requires at least one ClientRegistration to be configured through the use of a ReactiveClientRegistrationRepository bean. The following example configures an InMemoryReactiveClientRegistrationRepository bean using Spring Boot configuration properties: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

The main difference between the previous examples (Log Users In with OAuth2, Access Protected Resources) and this one is what is configured via the scope property, which combines the standard scopes openid and profile with the custom scopes message.read and message.write.spring-doc.cadn.net.cn

In addition to configuring Spring Security to support OAuth2 Client features, you will also need to decide how you will be accessing protected resources and configure your application accordingly. Spring Security provides implementations of ReactiveOAuth2AuthorizedClientManager for obtaining access tokens that can be used to access protected resources.spring-doc.cadn.net.cn

Spring Security registers a default ReactiveOAuth2AuthorizedClientManager bean for you when one does not exist.spring-doc.cadn.net.cn

The easiest way to use a ReactiveOAuth2AuthorizedClientManager is via an ExchangeFilterFunction that intercepts requests through a WebClient.spring-doc.cadn.net.cn

The following example uses the default ReactiveOAuth2AuthorizedClientManager to configure a WebClient capable of accessing protected resources by placing Bearer tokens in the Authorization header of each request:spring-doc.cadn.net.cn

Configure WebClient with ExchangeFilterFunction
@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()
	}

}

This configured WebClient can be used as in the following example:spring-doc.cadn.net.cn

Use WebClient to Access Protected Resources (Current User)
@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)

}

Unlike the previous example, notice that we do not need to tell Spring Security about the clientRegistrationId we’d like to use. This is because it can be derived from the currently logged in user.spring-doc.cadn.net.cn

Enable an Extension Grant Type

A common use case involves enabling and/or configuring an extension grant type. For example, Spring Security provides support for the jwt-bearer and token-exchange grant types, but does not enable them by default because they are not part of the core OAuth 2.0 specification.spring-doc.cadn.net.cn

With Spring Security 6.3 and later, we can simply publish a bean for one or more ReactiveOAuth2AuthorizedClientProvider and they will be picked up automatically. The following example simply enables the jwt-bearer grant type:spring-doc.cadn.net.cn

Enable jwt-bearer Grant Type
@Configuration
public class SecurityConfig {

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

}
@Configuration
class SecurityConfig {

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

}

A default ReactiveOAuth2AuthorizedClientManager will be published automatically by Spring Security when one is not already provided.spring-doc.cadn.net.cn

Any custom OAuth2AuthorizedClientProvider bean will also be picked up and applied to the provided ReactiveOAuth2AuthorizedClientManager after the default grant types.spring-doc.cadn.net.cn

In order to achieve the above configuration prior to Spring Security 6.3, we had to publish this bean ourselves and ensure we re-enabled default grant types as well. To understand what is being configured behind the scenes, here’s what the configuration might have looked like:spring-doc.cadn.net.cn

Enable jwt-bearer Grant Type (prior to 6.3)
@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
	}

}

Customize an Existing Grant Type

The ability to enable extension grant types by publishing a bean also provides the opportunity for customizing an existing grant type without the need to re-define the defaults. For example, if we want to customize the clock skew of the ReactiveOAuth2AuthorizedClientProvider for the client_credentials grant, we can simply publish a bean like so:spring-doc.cadn.net.cn

Customize Client Credentials Grant Type
@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
	}

}

Customize Token Request Parameters

The need to customize request parameters when obtaining an access token is fairly common. For example, let’s say we want to add a custom audience parameter to the token request because the provider requires this parameter for the authorization_code grant.spring-doc.cadn.net.cn

We can simply publish a bean of type ReactiveOAuth2AccessTokenResponseClient with the generic type OAuth2AuthorizationCodeGrantRequest and it will be used by Spring Security to configure OAuth2 Client components.spring-doc.cadn.net.cn

The following example customizes token request parameters for the authorization_code grant:spring-doc.cadn.net.cn

Customize Token Request Parameters for Authorization Code Grant
@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"
			}
		}
	}

}

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

As you can see, providing the ReactiveOAuth2AccessTokenResponseClient as a bean is quite convenient. When using the Spring Security DSL directly, we need to ensure that this customization is applied for both OAuth2 Login (if we are using this feature) and OAuth2 Client components. To understand what is being configured behind the scenes, here’s what the configuration would look like with the DSL:spring-doc.cadn.net.cn

Customize Token Request Parameters for Authorization Code Grant using the DSL
@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>> {
		// ...
	}

}

For other grant types we can publish additional ReactiveOAuth2AccessTokenResponseClient 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 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 automatically resolves the following generic types of ReactiveOAuth2AccessTokenResponseClient beans:spring-doc.cadn.net.cn

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

Customize the WebClient used by OAuth2 Client Components

Another common use case is the need to customize the WebClient used when obtaining an access token. We might need to do this to customize the underlying HTTP client library (via a custom ClientHttpConnector) to configure SSL settings or to apply proxy settings for a corporate network.spring-doc.cadn.net.cn

With Spring Security 6.3 and later, we can simply publish beans of type ReactiveOAuth2AccessTokenResponseClient and Spring Security will configure and publish a ReactiveOAuth2AuthorizedClientManager bean for us.spring-doc.cadn.net.cn

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

Customize WebClient for OAuth2 Client
@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 {
		// ...
	}

}

A default ReactiveOAuth2AuthorizedClientManager will be published automatically by Spring Security when one is not already provided.spring-doc.cadn.net.cn

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

Prior to Spring Security 6.3, we had to ensure this customization was applied to OAuth2 Client components ourselves. While we could publish a bean of type ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> for the authorization_code grant, we had to publish a bean of type ReactiveOAuth2AuthorizedClientManager 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 WebClient for OAuth2 Client (prior to 6.3)
@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 {
		// ...
	}

}

Further Reading

This 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