OAuth2 WebFlux
Spring Security 提供了全面的 OAuth 2.0 支持。本节讨论如何将 OAuth 2.0 集成到你的响应式应用程序中。
概述
Spring Security 的 OAuth 2.0 支持主要包括两个主要功能集:
OAuth2 登录 是一个非常强大的 OAuth2 客户端功能,在参考文档中值得单独设立一节。但是,它并不是一个独立的功能,需要 OAuth2 客户端才能运行。
这些功能集涵盖了OAuth 2.0 授权框架中定义的资源服务器和客户端角色,而授权服务器角色则由Spring 授权服务器涵盖,这是一个基于Spring Security构建的独立项目。
资源服务器和客户端角色在OAuth2中通常由一个或多个服务器端应用程序表示。此外,授权服务器角色可以由一个或多个第三方表示(例如,在组织内集中身份管理和/或认证时的情况)-or- 它也可以由一个应用程序表示(例如使用Spring Authorization Server的情况)。
例如,一个典型的基于OAuth2的微服务架构可能包括一个面向用户的客户端应用程序、几个提供REST API的后端资源服务器以及一个用于管理用户和认证问题的第三方授权服务器。也很常见的是,只有一个应用程序代表这些角色中的一个,并且需要与提供其他角色的一个或多个第三方进行集成。
Spring Security 处理这些场景以及更多。以下章节涵盖 Spring Security 提供的角色,并包含常见场景的示例。
OAuth2 资源服务器
本节包含OAuth2资源服务器功能的摘要和示例。有关完整的参考文档,请参见OAuth 2.0 资源服务器。
要开始使用,请将 spring-security-oauth2-resource-server
依赖添加到您的项目中。当使用 Spring Boot 时,添加以下 starter:
- Gradle
- Maven
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
参见 获取 Spring Security,以了解在不使用 Spring Boot 时的其他选项。
考虑以下 OAuth2 资源服务器的使用场景:
-
我想使用 OAuth2 保护对 API 的访问(授权服务器提供 JWT 或不透明的访问令牌)(authorization server provides JWT or opaque access token)
-
我想使用 JWT 保护对 API 的访问(自定义令牌)
用 OAuth2 访问令牌保护访问
使用 OAuth2 访问令牌来保护 API 的访问是非常常见的。在大多数情况下,Spring Security 只需要最少的配置即可用 OAuth2 来保护应用程序。
Spring Security 支持两种 Bearer
令牌,每种都使用不同的组件进行验证:
JWT 支持
以下示例使用 Spring Boot 配置属性配置了一个 ReactiveJwtDecoder
bean:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://my-auth-server.com
在使用 Spring Boot 时,仅需如此。Spring Boot 提供的默认配置等同于以下内容:
- Java
- Kotlin
@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 配置属性配置了一个 ReactiveOpaqueTokenIntrospector
bean:
spring:
security:
oauth2:
resourceserver:
opaquetoken:
introspection-uri: https://my-auth-server.com/oauth2/introspect
client-id: my-client-id
client-secret: my-client-secret
在使用 Spring Boot 时,仅需这些。Spring Boot 提供的默认配置等同于以下内容:
- Java
- Kotlin
@Configuration
@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 保护访问
保护对 API 的访问使用 JWT 是一个相当常见的目标,特别是当前端被开发为单页应用程序时。Spring Security 中的 OAuth2 资源服务器支持可以用于任何类型的 Bearer
令牌,包括自定义的 JWT。
要使用 JWT 保护 API,只需要一个 ReactiveJwtDecoder
bean,它用于验证签名和解码令牌。Spring Security 会自动使用提供的 bean 在 SecurityWebFilterChain
中配置保护。
以下示例使用 Spring Boot 配置属性配置了一个 ReactiveJwtDecoder
bean:
spring:
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:my-public-key.pub
你可以将公钥作为类路径资源提供(在本例中称为 my-public-key.pub
)。
在使用 Spring Boot 时,仅需要这样做。Spring Boot 提供的默认配置等同于以下内容:
- Java
- Kotlin
@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 提供了 JwtEncoder
接口及其一个实现,即 NimbusJwtEncoder
。
OAuth2 客户端
本节包含 OAuth2 客户端功能的概述和示例。有关完整的参考文档,请参阅 OAuth 2.0 客户端 和 OAuth 2.0 登录。
要开始使用,请将 spring-security-oauth2-client
依赖添加到您的项目中。当使用 Spring Boot 时,添加以下 starter:
- Gradle
- Maven
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
参见 获取 Spring Security,了解在不使用 Spring Boot 时的其他选项。
请考虑以下 OAuth2 Client 的使用场景:
使用 OAuth2 登录用户
要求用户通过 OAuth2 登录是非常常见的。OpenID Connect 1.0 提供了一种特殊的令牌,称为 id_token
,它旨在为 OAuth2 客户端提供执行用户身份验证和登录用户的能力。在某些情况下,OAuth2 可以直接用于登录用户(例如,一些不实现 OpenID Connect 的流行社交登录提供商,如 GitHub 和 Facebook)。
以下示例将应用程序配置为能够使用 OAuth2 或 OpenID Connect 登录用户的 OAuth2 客户端:
- Java
- Kotlin
@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 { }
}
}
}
除了上述配置之外,应用程序还需要通过使用 ReactiveClientRegistrationRepository
bean 来配置至少一个 ClientRegistration
。以下示例使用 Spring Boot 配置属性来配置一个 InMemoryReactiveClientRegistrationRepository
bean:
spring:
security:
oauth2:
client:
registration:
my-oidc-client:
provider: my-oidc-provider
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: openid,profile
provider:
my-oidc-provider:
issuer-uri: https://my-oidc-provider.com
通过上述配置,该应用程序现在支持两个额外的端点:
-
登录端点(例如
/oauth2/authorization/my-oidc-client
)用于启动登录并将重定向到第三方授权服务器。 -
重定向端点(例如
/login/oauth2/code/my-oidc-client
)由授权服务器用于重定向回客户端应用程序,并将包含一个code
参数,该参数用于通过访问令牌请求获取id_token
和/或access_token
。
上述配置中存在 openid
范围,表示应使用 OpenID Connect 1.0。这指示 Spring Security 在请求处理过程中使用 OIDC 特定的组件(如 OidcReactiveOAuth2UserService
)。如果没有这个范围,Spring Security 将使用 OAuth2 特定的组件(如 DefaultReactiveOAuth2UserService
)。
访问受保护的资源
向受OAuth2保护的第三方API发起请求是OAuth2 Client的核心用例之一。这可以通过授权一个客户端(在Spring Security中由OAuth2AuthorizedClient
类表示)并在出站请求的Authorization
头部放置一个Bearer
令牌来访问受保护的资源来实现。
以下示例将应用程序配置为能够从第三方 API 请求受保护资源的 OAuth2 客户端:
- Java
- Kotlin
@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()
结合使用的示例。
除了上述配置之外,应用程序还需要通过使用 ReactiveClientRegistrationRepository
bean 至少配置一个 ClientRegistration
。以下示例使用 Spring Boot 配置属性配置了一个 InMemoryReactiveClientRegistrationRepository
bean:
spring:
security:
oauth2:
client:
registration:
my-oauth2-client:
provider: my-auth-server
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: message.read,message.write
provider:
my-auth-server:
issuer-uri: https://my-auth-server.com
除了配置 Spring Security 以支持 OAuth2 Client 功能外,您还需要决定如何访问受保护的资源,并相应地配置您的应用程序。Spring Security 提供了 ReactiveOAuth2AuthorizedClientManager
的实现,用于获取可用于访问受保护资源的访问令牌。
当不存在时,Spring Security 会为你注册一个默认的 ReactiveOAuth2AuthorizedClientManager
bean。
使用 ReactiveOAuth2AuthorizedClientManager
的最简单方法是通过一个 ExchangeFilterFunction
,该函数通过 WebClient
拦截请求。
以下示例使用默认的 ReactiveOAuth2AuthorizedClientManager
来配置一个 WebClient
,该客户端能够通过将 Bearer
令牌放置在每个请求的 Authorization
标头中来访问受保护的资源:
- Java
- Kotlin
@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()
}
}
此配置的 WebClient
可以在如下示例中使用:
- Java
- Kotlin
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 登录时,授权服务器可能会提供一个可以直接用于访问受保护资源的访问令牌。这很方便,因为它只需要为这两种用例同时配置一个 ClientRegistration
。
本节将使用 OAuth2 登录用户和访问受保护的资源合并到一个配置中。其他高级场景也存在,例如为登录配置一个 ClientRegistration
,为访问受保护的资源配置另一个 ClientRegistration
。所有这些场景都会使用相同的基本配置。
以下示例将应用程序配置为能够登录用户并从第三方 API 请求受保护资源的 OAuth2 客户端:
- Java
- Kotlin
@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 { }
}
}
}
除了上述配置之外,应用程序还需要通过使用 ReactiveClientRegistrationRepository
bean 至少配置一个 ClientRegistration
。以下示例使用 Spring Boot 配置属性配置了一个 InMemoryReactiveClientRegistrationRepository
bean:
spring:
security:
oauth2:
client:
registration:
my-combined-client:
provider: my-auth-server
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: openid,profile,message.read,message.write
provider:
my-auth-server:
issuer-uri: https://my-auth-server.com
前面的示例(使用 OAuth2 登录用户、访问受保护的资源)与这个示例之间的主要区别在于通过 scope
属性配置的内容,它将标准范围 openid
和 profile
与自定义范围 message.read
和 message.write
结合在一起。
除了配置 Spring Security 以支持 OAuth2 Client 功能之外,您还需要决定如何访问受保护的资源,并相应地配置您的应用程序。Spring Security 提供了 ReactiveOAuth2AuthorizedClientManager
的实现,用于获取可用于访问受保护资源的访问令牌。
当不存在时,Spring Security 会为你注册一个默认的 ReactiveOAuth2AuthorizedClientManager
bean。
使用 ReactiveOAuth2AuthorizedClientManager
最简单的方法是通过一个 ExchangeFilterFunction
,该函数通过 WebClient
拦截请求。
以下示例使用默认的 ReactiveOAuth2AuthorizedClientManager
来配置一个 WebClient
,该客户端能够通过在每个请求的 Authorization
头中放置 Bearer
令牌来访问受保护的资源:
- Java
- Kotlin
@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()
}
}
此配置的 WebClient
可以在如下示例中使用:
- Java
- Kotlin
@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我们想要使用的clientRegistrationId
。这是因为可以从当前登录的用户中推导出来。
启用扩展授权类型
一个常见的用例是启用和/或配置扩展授权类型。例如,Spring Security 提供了对 jwt-bearer
和 token-exchange
授权类型的支持,但默认情况下并不启用它们,因为它们不属于核心的 OAuth 2.0 规范。
在 Spring Security 6.3 及更高版本中,我们只需发布一个或多个 ReactiveOAuth2AuthorizedClientProvider
的 bean,它们将被自动识别。以下示例仅启用 jwt-bearer
授权类型:
- Java
- Kotlin
@Configuration
public class SecurityConfig {
@Bean
public ReactiveOAuth2AuthorizedClientProvider jwtBearer() {
return new JwtBearerReactiveOAuth2AuthorizedClientProvider();
}
}
@Configuration
class SecurityConfig {
@Bean
fun jwtBearer(): ReactiveOAuth2AuthorizedClientProvider {
return JwtBearerReactiveOAuth2AuthorizedClientProvider()
}
}
当没有提供 ReactiveOAuth2AuthorizedClientManager
时,Spring Security 会自动发布一个默认的 ReactiveOAuth2AuthorizedClientManager
。
任何自定义的 OAuth2AuthorizedClientProvider
bean 也将在默认授权类型之后被选取,并应用于所提供的 ReactiveOAuth2AuthorizedClientManager
。
为了在 Spring Security 6.3 之前实现上述配置,我们必须自己发布这个 bean,并确保重新启用默认的授权类型。要了解幕后配置的内容,以下可能是配置的样子:
- Java
- Kotlin
@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 来启用扩展授权类型 的能力也提供了在无需重新定义默认值的情况下自定义现有授权类型的机会。例如,如果我们想要自定义 client_credentials
授权的 ReactiveOAuth2AuthorizedClientProvider
的时钟偏差,我们可以简单地发布一个 bean,如下所示:
- Java
- Kotlin
@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
}
}
自定义令牌请求参数
自定义获取访问令牌时的请求参数是一个相当常见的需求。例如,假设我们希望在令牌请求中添加一个自定义的 audience
参数,因为提供商在 authorization_code
授权类型中要求此参数。
我们可以简单地发布一个类型为 ReactiveOAuth2AccessTokenResponseClient
且泛型为 OAuth2AuthorizationCodeGrantRequest
的 bean,Spring Security 将使用它来配置 OAuth2 客户端组件。
以下示例自定义了 authorization_code
授权类型的令牌请求参数:
- Java
- Kotlin
@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"
}
}
}
}
注意,在这种情况下,我们不需要自定义 SecurityWebFilterChain
bean,可以使用默认设置。如果使用 Spring Boot 且没有其他自定义配置,我们实际上可以完全省略 SecurityWebFilterChain
bean。
正如你所见,将 ReactiveOAuth2AccessTokenResponseClient
作为 bean 提供是非常方便的。当直接使用 Spring Security DSL 时,我们需要确保此自定义应用于 OAuth2 登录(如果我们使用此功能)和 OAuth2 客户端组件。为了理解幕后配置的内容,以下是使用 DSL 的配置示例:
- Java
- Kotlin
@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>> {
// ...
}
}
对于其他授权类型,我们可以发布额外的 ReactiveOAuth2AccessTokenResponseClient
bean 来覆盖默认设置。例如,要自定义 client_credentials
授权的令牌请求,我们可以发布以下 bean:
- Java
- Kotlin
@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 会自动解析以下泛型类型的 ReactiveOAuth2AccessTokenResponseClient
bean:
-
OAuth2AuthorizationCodeGrantRequest
(参见WebClientReactiveAuthorizationCodeTokenResponseClient
) -
OAuth2RefreshTokenGrantRequest
(参见WebClientReactiveRefreshTokenTokenResponseClient
) -
OAuth2ClientCredentialsGrantRequest
(参见WebClientReactiveClientCredentialsTokenResponseClient
) -
OAuth2PasswordGrantRequest
(参见WebClientReactivePasswordTokenResponseClient
) -
JwtBearerGrantRequest
(参见WebClientReactiveJwtBearerTokenResponseClient
) -
TokenExchangeGrantRequest
(参见WebClientReactiveTokenExchangeTokenResponseClient
)
发布类型为 ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest>
的 bean 将自动启用 jwt-bearer
授权类型,无需单独配置。
发布类型为 ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest>
的 bean 将自动启用 token-exchange
授权类型,而无需单独配置。
自定义 OAuth2 客户端组件使用的 WebClient
另一个常见的用例是需要自定义用于获取访问令牌的 WebClient
。我们可能需要这样做来定制底层的 HTTP 客户端库(通过自定义 ClientHttpConnector
)以配置 SSL 设置或为公司网络应用代理设置。
从 Spring Security 6.3 及更高版本开始,我们只需发布类型为 ReactiveOAuth2AccessTokenResponseClient
的 bean,Spring Security 就会为我们配置并发布一个 ReactiveOAuth2AuthorizedClientManager
bean。
以下示例为所有支持的授权类型自定义 WebClient
:
- Java
- Kotlin
@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 {
// ...
}
}
当未提供 ReactiveOAuth2AuthorizedClientManager
时,Spring Security 会自动发布一个默认的 ReactiveOAuth2AuthorizedClientManager
。
请注意,在这种情况下,我们不需要自定义 SecurityWebFilterChain
bean,可以使用默认设置。如果使用 Spring Boot 且没有其他自定义设置,实际上我们可以完全省略 SecurityWebFilterChain
bean。
在Spring Security 6.3之前,我们必须确保这种自定义被应用到OAuth2客户端组件上。虽然我们可以为authorization_code
授权类型发布一个类型为ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
的bean,但我们需要为其他授权类型发布一个类型为ReactiveOAuth2AuthorizedClientManager
的bean。为了理解幕后配置的内容,以下是一个可能的配置示例:
- Java
- Kotlin
@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客户端和资源服务器的更多信息:
节总结
🗃️ OAuth2 登录
3 个项目
🗃️ OAuth2 客户端
4 个项目
🗃️ OAuth2 资源服务器
4 个项目