OAuth2 WebFlux
Spring Security 提供全面的 OAuth 2.0 支持。 本节讨论如何将 OAuth 2.0 集成到响应式应用程序中。
概述
Spring Security 的 OAuth 2.0 支持包括两个主要功能集:
OAuth2 登录是一个非常强大的 OAuth2 客户端功能,值得在参考文档中单独使用一节。 但是,它不作为独立功能存在,需要 OAuth2 客户端才能运行。 |
这些功能集涵盖了 OAuth 2.0 授权框架中定义的资源服务器和客户端角色,而授权服务器角色由 Spring 授权服务器涵盖,Spring Authorization Server 是一个基于 Spring Security 构建的独立项目。
OAuth2 中的资源服务器和客户端角色通常由一个或多个服务器端应用程序表示。 此外,授权服务器角色可以由一个或多个第三方表示(就像在组织内集中身份管理和/或身份验证时一样)-或者-它可以由应用程序表示(就像 Spring Authorization Server 的情况一样)。
例如,典型的基于 OAuth2 的微服务架构可能由一个面向用户的客户端应用程序、多个提供 REST API 的后端资源服务器和一个用于管理用户和身份验证问题的第三方授权服务器组成。 同样常见的情况是,单个应用程序仅代表其中一个角色,并且需要与提供其他角色的一个或多个第三方集成。
Spring Security 可以处理这些场景以及更多场景。 以下部分介绍了 Spring Security 提供的角色,并包含常见场景的示例。
OAuth2 资源服务器
本节包含 OAuth2 Resource Server 功能的摘要和示例。 请参阅 OAuth 2.0 Resource Server 以获取完整的参考文档。 |
首先,将依赖项添加到您的项目中。
使用 Spring Boot 时,添加以下 starter:spring-security-oauth2-resource-server
-
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 Boot 时的其他选项,请参见获取 Spring Security。 |
请考虑以下 OAuth2 Resource Server 的使用案例:
-
我想使用 OAuth2 保护对 API 的访问(授权服务器提供 JWT 或不透明访问令牌)
-
我想使用 JWT(自定义令牌)保护对 API 的访问
使用 OAuth2 访问令牌保护访问权限
使用 OAuth2 访问令牌保护对 API 的访问是很常见的。 在大多数情况下, Spring Security 只需要最少的配置即可使用 OAuth2 保护应用程序。
Spring Security 支持两种类型的令牌,每种令牌都使用不同的组件进行验证:Bearer
JWT 支持
下面的示例使用 Spring Boot 配置属性配置 Bean:ReactiveJwtDecoder
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 配置属性配置 Bean:ReactiveOpaqueTokenIntrospector
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 保护 Access
使用 JWT 保护对 API 的访问是一个相当常见的目标,尤其是当前端开发为单页应用程序时。
Spring Security 中的 OAuth2 资源服务器支持可用于任何类型的令牌,包括自定义 JWT。Bearer
使用 JWT 保护 API 所需的只是一个 Bean,它用于验证签名和解码令牌。
Spring Security 将自动使用提供的 bean 在 .ReactiveJwtDecoder
SecurityWebFilterChain
下面的示例使用 Spring Boot 配置属性配置 Bean:ReactiveJwtDecoder
spring:
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:my-public-key.pub
您可以将公钥作为 Classpath 资源(在本例中称为)提供。 |
使用 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 确实提供了接口以及一个实现,即 . |
OAuth2 客户端
本节包含 OAuth2 客户端功能摘要和示例。 请参阅 OAuth 2.0 客户端 和 OAuth 2.0 登录 以获取完整的参考文档。 |
首先,将依赖项添加到您的项目中。
使用 Spring Boot 时,添加以下 starter:spring-security-oauth2-client
-
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 Boot 时的其他选项,请参见获取 Spring Security。 |
请考虑 OAuth2 客户端的以下用例:
使用 OAuth2 登录用户
要求用户通过 OAuth2 登录是很常见的。OpenID Connect 1.0 提供了一个名为 the 的特殊令牌,该令牌旨在为 OAuth2 客户端提供执行用户身份验证和登录用户的能力。
在某些情况下,OAuth2 可以直接用于登录用户(例如,GitHub 和 Facebook 等不实施 OpenID Connect 的流行社交登录提供商就是这种情况)。id_token
以下示例将应用程序配置为充当 OAuth2 客户端,能够使用 OAuth2 或 OpenID Connect 将用户登录:
-
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 { }
}
}
}
除了上述配置之外,应用程序还要求至少使用 bean 配置一个。
以下示例使用 Spring Boot 配置属性配置 Bean:ClientRegistration
ReactiveClientRegistrationRepository
InMemoryReactiveClientRegistrationRepository
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
上述配置中存在 scope 表示应使用 OpenID Connect 1.0。
这指示 Spring Security 在请求处理期间使用特定于 OIDC 的组件(例如 )。
如果没有此范围,Spring Security 将改用特定于 OAuth2 的组件(例如 )。 |
访问受保护的资源
向受 OAuth2 保护的第三方 API 发出请求是 OAuth2 客户端的核心用例。
这是通过授权客户端(由 Spring Security 中的类表示)并通过在出站请求的标头中放置令牌来访问受保护的资源来实现的。OAuth2AuthorizedClient
Bearer
Authorization
以下示例将应用程序配置为能够从第三方 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 { }
}
}
}
上面的示例没有提供登录用户的方法。
您可以使用任何其他登录机制(如 )。
有关与 的组合示例,请参阅下一节。 |
除了上述配置之外,应用程序还要求至少使用 bean 配置一个。
以下示例使用 Spring Boot 配置属性配置 Bean:ClientRegistration
ReactiveClientRegistrationRepository
InMemoryReactiveClientRegistrationRepository
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 提供了用于获取可用于访问受保护资源的访问令牌的实现。ReactiveOAuth2AuthorizedClientManager
Spring Security 在不存在默认 bean 时为您注册一个默认 bean。 |
使用 a 的最简单方法是通过 an 通过 .ReactiveOAuth2AuthorizedClientManager
ExchangeFilterFunction
WebClient
以下示例使用默认值来配置能够访问受保护资源的功能,方法是在每个请求的标头中放置令牌:ReactiveOAuth2AuthorizedClientManager
WebClient
Bearer
Authorization
WebClient
ExchangeFilterFunction
-
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
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
本部分将 Log Users In with OAuth2 和 Access Protected Resources 合并到一个配置中。
存在其他高级方案,例如配置一个用于登录,另一个用于访问受保护的资源。
所有此类场景都将使用相同的基本配置。 |
以下示例将应用程序配置为 OAuth2 客户端,该客户端能够使用户登录并从第三方 API 请求受保护的资源:
-
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 { }
}
}
}
除了上述配置之外,应用程序还要求至少使用 bean 配置一个。
以下示例使用 Spring Boot 配置属性配置 Bean:ClientRegistration
ReactiveClientRegistrationRepository
InMemoryReactiveClientRegistrationRepository
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 登录用户、访问受保护的资源)与此示例之间的主要区别在于通过属性配置的内容,该属性结合了标准范围和自定义范围 和 。 |
除了配置 Spring Security 以支持 OAuth2 客户端功能之外,您还需要决定如何访问受保护的资源并相应地配置您的应用程序。
Spring Security 提供了用于获取可用于访问受保护资源的访问令牌的实现。ReactiveOAuth2AuthorizedClientManager
Spring Security 在不存在默认 bean 时为您注册一个默认 bean。 |
使用 a 的最简单方法是通过 an 通过 .ReactiveOAuth2AuthorizedClientManager
ExchangeFilterFunction
WebClient
以下示例使用默认值来配置能够访问受保护资源的功能,方法是在每个请求的标头中放置令牌:ReactiveOAuth2AuthorizedClientManager
WebClient
Bearer
Authorization
WebClient
ExchangeFilterFunction
-
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
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 我们想要使用的。
这是因为它可以从当前登录的用户派生。 |
启用扩展授权类型
一个常见的使用案例涉及启用和/或配置扩展授权类型。
例如, Spring Security 提供对 和 grant 类型的支持,但默认情况下不启用它们,因为它们不是核心 OAuth 2.0 规范的一部分。jwt-bearer
token-exchange
使用 Spring Security 6.3 及更高版本,我们可以简单地为一个或多个发布一个 bean,它们将被自动选取。
以下示例仅启用 grant 类型:ReactiveOAuth2AuthorizedClientProvider
jwt-bearer
jwt-bearer
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public ReactiveOAuth2AuthorizedClientProvider jwtBearer() {
return new JwtBearerReactiveOAuth2AuthorizedClientProvider();
}
}
@Configuration
class SecurityConfig {
@Bean
fun jwtBearer(): ReactiveOAuth2AuthorizedClientProvider {
return JwtBearerReactiveOAuth2AuthorizedClientProvider()
}
}
如果尚未提供默认值,则 Spring Security 将自动发布默认值。ReactiveOAuth2AuthorizedClientManager
任何自定义 bean 也将被选取并应用于默认授权类型之后提供的 bean。 |
为了在 Spring Security 6.3 之前实现上述配置,我们必须自己发布这个 bean 并确保我们也重新启用了默认授权类型。 要了解幕后配置的内容,以下是配置可能的样子:
jwt-bearer
-
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 来启用扩展授权类型的功能还提供了自定义现有授权类型的机会,而无需重新定义默认值。
例如,如果我们想自定义 for the grant 的 clock skew,我们可以简单地发布一个 bean,如下所示:ReactiveOAuth2AuthorizedClientProvider
client_credentials
-
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
}
}
自定义 Token 请求参数
在获取访问令牌时,需要自定义请求参数是相当普遍的。
例如,假设我们想向令牌请求添加自定义参数,因为提供商需要此参数进行授权。audience
authorization_code
我们可以简单地发布一个泛型类型的 bean,Spring Security 将使用它来配置 OAuth2 客户端组件。ReactiveOAuth2AccessTokenResponseClient
OAuth2AuthorizationCodeGrantRequest
以下示例自定义授权的 token 请求参数: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"
}
}
}
}
请注意,在这种情况下,我们不需要自定义 bean,并且可以坚持使用默认值。
如果使用 Spring Boot 而没有额外的自定义,我们实际上可以完全省略 bean。 |
如您所见,提供 as a bean 非常方便。
当直接使用 Spring Security DSL 时,我们需要确保此自定义同时应用于 OAuth2 登录(如果我们使用此功能)和 OAuth2 客户端组件。
要了解幕后配置的内容,以下是 DSL 的配置情况:ReactiveOAuth2AccessTokenResponseClient
-
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>> {
// ...
}
}
对于其他授权类型,我们可以发布其他 bean 来覆盖默认值。
例如,要自定义授权的令牌请求,我们可以发布以下 bean:ReactiveOAuth2AccessTokenResponseClient
client_credentials
-
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 会自动解析以下泛型 bean:ReactiveOAuth2AccessTokenResponseClient
-
OAuth2AuthorizationCodeGrantRequest
(参见WebClientReactiveAuthorizationCodeTokenResponseClient
) -
OAuth2RefreshTokenGrantRequest
(参见WebClientReactiveRefreshTokenTokenResponseClient
) -
OAuth2ClientCredentialsGrantRequest
(参见WebClientReactiveClientCredentialsTokenResponseClient
) -
OAuth2PasswordGrantRequest
(参见WebClientReactivePasswordTokenResponseClient
) -
JwtBearerGrantRequest
(参见WebClientReactiveJwtBearerTokenResponseClient
) -
TokenExchangeGrantRequest
(参见WebClientReactiveTokenExchangeTokenResponseClient
)
发布 类型的 bean 将自动启用 grant 类型,而无需单独配置它。 |
发布 类型的 bean 将自动启用 grant 类型,而无需单独配置它。 |
自定义 OAuth2 客户端组件使用的WebClient
另一个常见的用例是在获取访问令牌时需要自定义 used。
我们可能需要执行此操作来自定义基础 HTTP 客户端库(通过自定义)以配置 SSL 设置或为公司网络应用代理设置。WebClient
ClientHttpConnector
使用 Spring Security 6.3 及更高版本,我们只需发布 bean 类型,Spring Security 将为我们配置和发布 bean。ReactiveOAuth2AccessTokenResponseClient
ReactiveOAuth2AuthorizedClientManager
以下示例为所有受支持的授权类型自定义 :WebClient
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 {
// ...
}
}
如果尚未提供默认值,则 Spring Security 将自动发布默认值。ReactiveOAuth2AuthorizedClientManager
请注意,在这种情况下,我们不需要自定义 bean,并且可以坚持使用默认值。
如果使用 Spring Boot 而没有额外的自定义,我们实际上可以完全省略 bean。 |
在 Spring Security 6.3 之前,我们必须确保自己将此自定义应用于 OAuth2 客户端组件。
虽然我们可以为授权发布 type 的 bean,但我们必须为其他授权类型发布 type 的 bean。
要了解幕后配置的内容,以下是配置可能的样子:ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
authorization_code
ReactiveOAuth2AuthorizedClientManager
WebClient
-
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 {
// ...
}
}