OAuth2
Spring Security 提供了对 OAuth 2.0 的全面支持。本节讨论如何将 OAuth 2.0 集成到基于 Servlet 的应用中。
概览
Spring Security 的 OAuth 2.0 支持包含两个主要的功能集
OAuth2 登录 是一个非常强大的 OAuth2 Client 特性,在参考文档中有专门的章节介绍。然而,它并非独立特性,需要 OAuth2 Client 才能工作。 |
这些功能集涵盖了 OAuth 2.0 授权框架 中定义的 资源服务器 和 客户端 角色,而 授权服务器 角色由 Spring Authorization Server 负责,Spring Authorization Server 是一个基于 Spring Security 构建的独立项目。
OAuth2 中的 资源服务器 和 客户端 角色通常由一个或多个服务器端应用表示。此外,授权服务器 角色可以由一个或多个第三方表示(例如,在组织内集中管理身份和/或认证的情况下)**-或者-** 它可以由一个应用表示(例如 Spring Authorization Server 的情况)。
例如,典型的基于 OAuth2 的微服务架构可能由一个面向用户的客户端应用、几个提供 REST API 的后端资源服务器以及一个用于管理用户和认证问题的第三方授权服务器组成。一个应用仅扮演这些角色之一,并需要与提供其他角色的一或多个第三方进行集成,这种情况也很常见。
Spring Security 可以处理这些以及更多场景。以下章节涵盖了 Spring Security 提供的角色,并包含常见场景的示例。
OAuth2 Resource Server
本节总结了 OAuth2 Resource Server 的特性并提供示例。有关完整的参考文档,请参阅 OAuth 2.0 Resource Server。 |
要开始使用,请将 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 Boot 时,请参阅 获取 Spring Security 以了解更多选项。 |
考虑 OAuth2 Resource Server 的以下用例
-
我想 使用 OAuth2 保护 API 访问 (授权服务器提供 JWT 或不透明访问令牌)
-
我想 使用 JWT 保护 API 访问 (自定义令牌)
使用 OAuth2 Access Token 保护访问
使用 OAuth2 访问令牌保护 API 访问非常常见。在大多数情况下,Spring Security 只需要最少的配置即可使用 OAuth2 保护应用。
Spring Security 支持两种类型的 Bearer
令牌,每种都使用不同的组件进行验证
JWT 支持
以下示例使用 Spring Boot 配置属性配置 JwtDecoder
bean
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://my-auth-server.com
使用 Spring Boot 时,只需要这些配置。Spring Boot 提供的默认配置等同于以下内容
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
return JwtDecoders.fromIssuerLocation("https://my-auth-server.com");
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt { }
}
}
return http.build()
}
@Bean
fun jwtDecoder(): JwtDecoder {
return JwtDecoders.fromIssuerLocation("https://my-auth-server.com")
}
}
不透明令牌支持
以下示例使用 Spring Boot 配置属性配置 OpaqueTokenIntrospector
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
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.opaqueToken(Customizer.withDefaults())
);
return http.build();
}
@Bean
public OpaqueTokenIntrospector opaqueTokenIntrospector() {
return new SpringOpaqueTokenIntrospector(
"https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret");
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
opaqueToken { }
}
}
return http.build()
}
@Bean
fun opaqueTokenIntrospector(): OpaqueTokenIntrospector {
return SpringOpaqueTokenIntrospector(
"https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret"
)
}
}
使用自定义 JWT 保护访问
使用 JWT 保护 API 访问是一个相当普遍的目标,特别是在前端开发为单页应用时。Spring Security 中的 OAuth2 Resource Server 支持可用于任何类型的 Bearer
令牌,包括自定义 JWT。
使用 JWT 保护 API 所需的全部是 JwtDecoder
bean,它用于验证签名和解码令牌。Spring Security 将自动使用提供的 bean 在 SecurityFilterChain
中配置保护。
以下示例使用 Spring Boot 配置属性配置 JwtDecoder
bean
spring:
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:my-public-key.pub
您可以将公钥作为类路径资源提供(在此示例中称为 |
使用 Spring Boot 时,只需要这些配置。Spring Boot 提供的默认配置等同于以下内容
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(publicKey()).build();
}
private RSAPublicKey publicKey() {
// ...
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt { }
}
}
return http.build()
}
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withPublicKey(publicKey()).build()
}
private fun publicKey(): RSAPublicKey {
// ...
}
}
Spring Security 不提供用于生成令牌的端点。但是,Spring Security 提供了 |
OAuth2 Client
本节总结了 OAuth2 Client 特性并提供示例。有关完整的参考文档,请参阅 OAuth 2.0 Client 和 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 Boot 时,请参阅 获取 Spring Security 以了解更多选项。 |
考虑 OAuth2 Client 的以下用例
-
我想 使用
RestClient
为用户获取访问令牌 以访问第三方 API -
我想 使用
WebClient
为用户获取访问令牌 以访问第三方 API -
我想 同时做这两件事 (登录用户 并 访问第三方 API)
-
我想 使用
client_credentials
授权类型 为每个应用获取一个单一令牌 -
我想 启用扩展授权类型
-
我想 自定义现有的授权类型
-
我想 自定义令牌请求参数
使用 OAuth2 登录用户
要求用户通过 OAuth2 登录非常常见。OpenID Connect 1.0 提供了一种名为 id_token
的特殊令牌,旨在为 OAuth2 Client 提供执行用户身份验证和登录用户的功能。在某些情况下,OAuth2 可以直接用于登录用户(例如某些流行的社交登录提供商,它们不实现 OpenID Connect,如 GitHub 和 Facebook)。
以下示例配置应用作为 OAuth2 Client,能够使用 OAuth2 或 OpenID Connect 登录用户
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Login(Customizer.withDefaults());
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
oauth2Login { }
}
return http.build()
}
}
除了上述配置,应用还需要至少一个 ClientRegistration
,这通过使用 ClientRegistrationRepository
bean 进行配置。以下示例使用 Spring Boot 配置属性配置 InMemoryClientRegistrationRepository
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
。
上述配置中存在 |
访问受保护资源
对受 OAuth2 保护的第三方 API 发出请求是 OAuth2 Client 的一个核心用例。这通过授权客户端(在 Spring Security 中由 OAuth2AuthorizedClient
类表示)并在出站请求的 Authorization
头部中放置 Bearer
令牌来访问受保护资源来实现。
以下示例配置应用作为 OAuth2 Client,能够从第三方 API 请求受保护资源
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Client(Customizer.withDefaults());
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
oauth2Client { }
}
return http.build()
}
}
上述示例不提供用户登录的方式。您可以使用任何其他登录机制(例如 |
除了上述配置,应用还需要至少一个 ClientRegistration
,这通过使用 ClientRegistrationRepository
bean 进行配置。以下示例使用 Spring Boot 配置属性配置 InMemoryClientRegistrationRepository
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 提供了 OAuth2AuthorizedClientManager
的实现,用于获取可用于访问受保护资源的访问令牌。
当不存在 |
使用 OAuth2AuthorizedClientManager
的最简单方法是通过一个 ClientHttpRequestInterceptor
,它拦截通过 RestClient
发送的请求。当 spring-web
位于类路径上时,RestClient
已经可用。
以下示例使用默认的 OAuth2AuthorizedClientManager
配置一个 RestClient
,该 RestClient
能够通过在每个请求的 Authorization
头部放置 Bearer
令牌来访问受保护资源。
ClientHttpRequestInterceptor
配置 RestClient
-
Java
-
Kotlin
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}
}
@Configuration
class RestClientConfig {
@Bean
fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build()
}
}
配置好的 RestClient
可以按照以下示例使用
RestClient
访问受保护资源-
Java
-
Kotlin
import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;
@RestController
public class MessagesController {
private final RestClient restClient;
public MessagesController(RestClient restClient) {
this.restClient = restClient;
}
@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
Message[] messages = this.restClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.body(Message[].class);
return ResponseEntity.ok(Arrays.asList(messages));
}
public record Message(String message) {
}
}
import org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId
import org.springframework.web.client.body
@RestController
class MessagesController(private val restClient: RestClient) {
@GetMapping("/messages")
fun messages(): ResponseEntity<List<Message>> {
val messages = restClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.body<Array<Message>>()!!
.toList()
return ResponseEntity.ok(messages)
}
data class Message(val message: String)
}
使用 WebClient
访问受保护资源
对受 OAuth2 保护的第三方 API 发出请求是 OAuth2 Client 的一个核心用例。这通过授权客户端(在 Spring Security 中由 OAuth2AuthorizedClient
类表示)并在出站请求的 Authorization
头部中放置 Bearer
令牌来访问受保护资源来实现。
以下示例配置应用作为 OAuth2 Client,能够从第三方 API 请求受保护资源
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Client(Customizer.withDefaults());
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
oauth2Client { }
}
return http.build()
}
}
上述示例不提供用户登录的方式。您可以使用任何其他登录机制(例如 |
除了上述配置,应用还需要至少一个 ClientRegistration
,这通过使用 ClientRegistrationRepository
bean 进行配置。以下示例使用 Spring Boot 配置属性配置 InMemoryClientRegistrationRepository
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 提供了 OAuth2AuthorizedClientManager
的实现,用于获取可用于访问受保护资源的访问令牌。
当不存在 |
除了配置 RestClient
,另一种使用 OAuth2AuthorizedClientManager
的方法是通过一个 ExchangeFilterFunction
,它拦截通过 WebClient
发送的请求。要使用 WebClient
,您需要添加 spring-webflux
依赖以及一个响应式客户端实现
-
Gradle
-
Maven
implementation 'org.springframework:spring-webflux'
implementation 'io.projectreactor.netty:reactor-netty'
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
以下示例使用默认的 OAuth2AuthorizedClientManager
配置一个 WebClient
,该 WebClient
能够通过在每个请求的 Authorization
头部放置 Bearer
令牌来访问受保护资源。
ExchangeFilterFunction
配置 WebClient
-
Java
-
Kotlin
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction filter =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(filter.oauth2Configuration())
.build();
}
}
@Configuration
class WebClientConfig {
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager): WebClient {
val filter = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
return WebClient.builder()
.apply(filter.oauth2Configuration())
.build()
}
}
配置好的 WebClient
可以按照以下示例使用
WebClient
访问受保护资源-
Java
-
Kotlin
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;
@RestController
public class MessagesController {
private final WebClient webClient;
public MessagesController(WebClient webClient) {
this.webClient = webClient;
}
@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
return this.webClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.toEntityList(Message.class)
.block();
}
public record Message(String message) {
}
}
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId
@RestController
class MessagesController(private val webClient: WebClient) {
@GetMapping("/messages")
fun messages(): ResponseEntity<List<Message>> {
return webClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.toEntityList<Message>()
.block()!!
}
data class Message(val message: String)
}
访问当前用户的受保护资源
当用户通过 OAuth2 或 OpenID Connect 登录时,授权服务器可能会提供一个可直接用于访问受保护资源的访问令牌。这非常方便,因为它只需要配置一个 ClientRegistration
即可同时支持这两种用例。
本节将 使用 OAuth2 登录用户 和 访问受保护资源 合并到一个配置中。还存在其他高级场景,例如为一个 |
以下示例配置应用作为 OAuth2 Client,能够登录用户 并 从第三方 API 请求受保护资源
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Login(Customizer.withDefaults())
.oauth2Client(Customizer.withDefaults());
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
oauth2Login { }
oauth2Client { }
}
return http.build()
}
}
除了上述配置,应用还需要至少一个 ClientRegistration
,这通过使用 ClientRegistrationRepository
bean 进行配置。以下示例使用 Spring Boot 配置属性配置 InMemoryClientRegistrationRepository
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 登录用户、访问受保护资源)与本例的主要区别在于通过 |
除了配置 Spring Security 以支持 OAuth2 Client 特性外,您还需要决定如何访问受保护资源并相应地配置您的应用。Spring Security 提供了 OAuth2AuthorizedClientManager
的实现,用于获取可用于访问受保护资源的访问令牌。
当不存在 |
使用 OAuth2AuthorizedClientManager
的最简单方法是通过一个 ClientHttpRequestInterceptor
,它拦截通过 RestClient
发送的请求。当 spring-web
位于类路径上时,RestClient
已经可用。
以下示例使用默认的 OAuth2AuthorizedClientManager
配置一个 RestClient
,该 RestClient
能够通过在每个请求的 Authorization
头部放置 Bearer
令牌来访问受保护资源。
ClientHttpRequestInterceptor
配置 RestClient
-
Java
-
Kotlin
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
requestInterceptor.setClientRegistrationIdResolver(clientRegistrationIdResolver());
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}
private static ClientRegistrationIdResolver clientRegistrationIdResolver() {
return (request) -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return (authentication instanceof OAuth2AuthenticationToken principal)
? principal.getAuthorizedClientRegistrationId()
: null;
};
}
}
@Configuration
class RestClientConfig {
@Bean
fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
requestInterceptor.setClientRegistrationIdResolver(clientRegistrationIdResolver())
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build()
}
private fun clientRegistrationIdResolver(): OAuth2ClientHttpRequestInterceptor.ClientRegistrationIdResolver {
return OAuth2ClientHttpRequestInterceptor.ClientRegistrationIdResolver { request ->
val authentication = SecurityContextHolder.getContext().authentication
if (authentication is OAuth2AuthenticationToken) {
authentication.authorizedClientRegistrationId
} else {
null
}
}
}
}
配置好的 RestClient
可以按照以下示例使用
RestClient
访问受保护资源(当前用户)-
Java
-
Kotlin
@RestController
public class MessagesController {
private final RestClient restClient;
public MessagesController(RestClient restClient) {
this.restClient = restClient;
}
@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
Message[] messages = this.restClient.get()
.uri("http://localhost:8090/messages")
.retrieve()
.body(Message[].class);
return ResponseEntity.ok(Arrays.asList(messages));
}
public record Message(String message) {
}
}
import org.springframework.web.client.body
@RestController
class MessagesController(private val restClient: RestClient) {
@GetMapping("/messages")
fun messages(): ResponseEntity<List<Message>> {
val messages = restClient.get()
.uri("http://localhost:8090/messages")
.retrieve()
.body<Array<Message>>()!!
.toList()
return ResponseEntity.ok(messages)
}
data class Message(val message: String)
}
与上一个示例不同,请注意我们无需告知 Spring Security 我们希望使用的 |
使用客户端凭证授权类型
本节重点介绍客户端凭证授权类型的其他注意事项。有关所有授权类型的通用设置和用法,请参阅访问受保护资源。 |
遵循 客户端凭证授权 允许客户端代表自身获取 access_token
。客户端凭证授权是一种不涉及资源所有者(即用户)的简单流程。
值得注意的是,客户端凭证授权的典型用法意味着任何请求(或用户)都可能获取访问令牌并向资源服务器发出受保护资源请求。在设计应用时务必谨慎,以确保用户不能发出未经授权的请求,因为每个请求都将能够获取访问令牌。 |
在用户可以登录的 Web 应用中获取访问令牌时,Spring Security 的默认行为是按用户获取访问令牌。
默认情况下,访问令牌的作用域限定在当前用户的 principal name,这意味着每个用户都将收到一个唯一的访问令牌。 |
使用客户端凭证授权的客户端通常要求访问令牌的作用域限定在应用级别,而不是针对单个用户,这样每个应用只有一个访问令牌。为了将访问令牌的作用域限定在应用级别,您需要设置一个用于解析自定义 principal name 的策略。以下示例通过使用 RequestAttributePrincipalResolver
配置 RestClient
来实现这一点
client_credentials
配置 RestClient
-
Java
-
Kotlin
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
requestInterceptor.setPrincipalResolver(new RequestAttributePrincipalResolver());
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}
}
@Configuration
class RestClientConfig {
@Bean
fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
requestInterceptor.setPrincipalResolver(RequestAttributePrincipalResolver())
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build()
}
}
通过上述配置,可以为每个请求指定一个 principal name。以下示例演示了如何通过指定 principal name 将访问令牌的作用域限定在应用级别
-
Java
-
Kotlin
import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;
import static org.springframework.security.oauth2.client.web.client.RequestAttributePrincipalResolver.principal;
@RestController
public class MessagesController {
private final RestClient restClient;
public MessagesController(RestClient restClient) {
this.restClient = restClient;
}
@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
Message[] messages = this.restClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.attributes(principal("my-application"))
.retrieve()
.body(Message[].class);
return ResponseEntity.ok(Arrays.asList(messages));
}
public record Message(String message) {
}
}
import org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId
import org.springframework.security.oauth2.client.web.client.RequestAttributePrincipalResolver.principal
import org.springframework.web.client.body
@RestController
class MessagesController(private val restClient: RestClient) {
@GetMapping("/messages")
fun messages(): ResponseEntity<List<Message>> {
val messages = restClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.attributes(principal("my-application"))
.retrieve()
.body<Array<Message>>()!!
.toList()
return ResponseEntity.ok(messages)
}
data class Message(val message: String)
}
如上例所示,通过属性指定 principal name 时,将只有一个访问令牌,并且该令牌将用于所有请求。 |
启用扩展授权类型
启用和/或配置扩展授权类型是一个常见的用例。例如,Spring Security 支持 jwt-bearer
和 token-exchange
授权类型,但默认不启用它们,因为它们不是核心 OAuth 2.0 规范的一部分。
使用 Spring Security 6.2 及更高版本,我们可以简单地发布一个或多个 OAuth2AuthorizedClientProvider
的 bean,它们将被自动识别。以下示例仅启用 jwt-bearer
授权类型
jwt-bearer
授权类型-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AuthorizedClientProvider jwtBearer() {
return new JwtBearerOAuth2AuthorizedClientProvider();
}
}
@Configuration
class SecurityConfig {
@Bean
fun jwtBearer(): OAuth2AuthorizedClientProvider {
return JwtBearerOAuth2AuthorizedClientProvider()
}
}
当没有提供 OAuth2AuthorizedClientManager
bean 时,Spring Security 将自动发布一个默认的。
任何自定义的 |
为了在 Spring Security 6.2 之前实现上述配置,我们必须自己发布此 bean,并确保也重新启用默认授权类型。为了理解幕后配置的内容,配置可能如下所示
jwt-bearer
授权类型(6.2 之前)-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.provider(new JwtBearerOAuth2AuthorizedClientProvider())
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
@Configuration
class SecurityConfig {
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientRepository: OAuth2AuthorizedClientRepository
): OAuth2AuthorizedClientManager {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.provider(JwtBearerOAuth2AuthorizedClientProvider())
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository
)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
}
自定义现有授权类型
通过发布 bean 来启用扩展授权类型 的能力也为自定义现有授权类型提供了机会,而无需重新定义默认值。例如,如果我们要自定义 OAuth2AuthorizedClientProvider
的时钟偏差以用于 client_credentials
授权,我们可以简单地发布一个 bean,如下所示
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AuthorizedClientProvider clientCredentials() {
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider =
new ClientCredentialsOAuth2AuthorizedClientProvider();
authorizedClientProvider.setClockSkew(Duration.ofMinutes(5));
return authorizedClientProvider;
}
}
@Configuration
class SecurityConfig {
@Bean
fun clientCredentials(): OAuth2AuthorizedClientProvider {
val authorizedClientProvider = ClientCredentialsOAuth2AuthorizedClientProvider()
authorizedClientProvider.setClockSkew(Duration.ofMinutes(5))
return authorizedClientProvider
}
}
自定义令牌请求参数
在获取访问令牌时自定义请求参数的需求相当常见。例如,假设我们想向令牌请求添加一个自定义的 audience
参数,因为提供商对 authorization_code
授权需要此参数。
使用 Spring Security 6.2 及更高版本,我们可以简单地发布一个类型为 OAuth2AccessTokenResponseClient
且泛型类型为 OAuth2AuthorizationCodeGrantRequest
的 bean,Spring Security 将使用它来配置 OAuth2 Client 组件。
以下示例在不使用 DSL 的情况下自定义 authorization_code
授权的令牌请求参数
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
new OAuth2AuthorizationCodeGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(parametersConverter());
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
return accessTokenResponseClient;
}
private static Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter() {
return (grantRequest) -> {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set("audience", "xyz_value");
return parameters;
};
}
}
@Configuration
class SecurityConfig {
@Bean
fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter()
requestEntityConverter.addParametersConverter(parametersConverter())
val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter)
return accessTokenResponseClient
}
private fun parametersConverter(): Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> {
return Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> { grantRequest ->
LinkedMultiValueMap<String, String>().also { parameters ->
parameters["audience"] = "xyz_value"
}
}
}
}
请注意,在这种情况下,我们不需要自定义 |
在 Spring Security 6.2 之前,我们必须确保此自定义应用于 OAuth2 Login(如果使用此功能)和 OAuth2 Client 组件,使用 Spring Security DSL。为了理解幕后配置的内容,配置可能如下所示
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
new OAuth2AuthorizationCodeGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(parametersConverter());
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2Login((oauth2Login) -> oauth2Login
.tokenEndpoint((tokenEndpoint) -> tokenEndpoint
.accessTokenResponseClient(accessTokenResponseClient)
)
)
.oauth2Client((oauth2Client) -> oauth2Client
.authorizationCodeGrant((authorizationCode) -> authorizationCode
.accessTokenResponseClient(accessTokenResponseClient)
)
);
return http.build();
}
private static Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter() {
// ...
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter()
requestEntityConverter.addParametersConverter(parametersConverter())
val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
tokenResponseClient.setRequestEntityConverter(requestEntityConverter)
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
oauth2Login {
tokenEndpoint {
accessTokenResponseClient = tokenResponseClient
}
}
oauth2Client {
authorizationCodeGrant {
accessTokenResponseClient = tokenResponseClient
}
}
}
return http.build()
}
private fun parametersConverter(): Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> {
// ...
}
}
对于其他授权类型,我们可以发布额外的 OAuth2AccessTokenResponseClient
bean 来覆盖默认设置。例如,要自定义 client_credentials
授权的令牌请求,我们可以发布以下 bean
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter =
new OAuth2ClientCredentialsGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(parametersConverter());
DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
return accessTokenResponseClient;
}
private static Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> parametersConverter() {
// ...
}
}
@Configuration
class SecurityConfig {
@Bean
fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
val requestEntityConverter = OAuth2ClientCredentialsGrantRequestEntityConverter()
requestEntityConverter.addParametersConverter(parametersConverter())
val accessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter)
return accessTokenResponseClient
}
private fun parametersConverter(): Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> {
// ...
}
}
Spring Security 自动解析以下 OAuth2AccessTokenResponseClient
bean 的泛型类型
-
OAuth2AuthorizationCodeGrantRequest
(参见DefaultAuthorizationCodeTokenResponseClient
) -
OAuth2RefreshTokenGrantRequest
(参见DefaultRefreshTokenTokenResponseClient
) -
OAuth2ClientCredentialsGrantRequest
(参见DefaultClientCredentialsTokenResponseClient
) -
OAuth2PasswordGrantRequest
(参见DefaultPasswordTokenResponseClient
) -
JwtBearerGrantRequest
(参见DefaultJwtBearerTokenResponseClient
) -
TokenExchangeGrantRequest
(参见DefaultTokenExchangeTokenResponseClient
)
发布一个类型为 |
发布一个类型为 |
自定义 OAuth2 Client 组件使用的 RestOperations
另一个常见的用例是需要自定义获取访问令牌时使用的 RestOperations
。我们可能需要这样做来自定义响应的处理(通过自定义的 HttpMessageConverter
)或为公司网络应用代理设置(通过自定义的 ClientHttpRequestFactory
)。
使用 Spring Security 6.2 及更高版本,我们可以简单地发布类型为 OAuth2AccessTokenResponseClient
的 bean,Spring Security 将为我们配置并发布一个 OAuth2AuthorizedClientManager
bean。
以下示例为所有支持的授权类型自定义 RestOperations
RestOperations
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
DefaultRefreshTokenTokenResponseClient accessTokenResponseClient =
new DefaultRefreshTokenTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
DefaultPasswordTokenResponseClient accessTokenResponseClient =
new DefaultPasswordTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
DefaultJwtBearerTokenResponseClient accessTokenResponseClient =
new DefaultJwtBearerTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
DefaultTokenExchangeTokenResponseClient accessTokenResponseClient =
new DefaultTokenExchangeTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public RestTemplate restTemplate() {
// ...
}
}
@Configuration
class SecurityConfig {
@Bean
fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun refreshTokenAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
val accessTokenResponseClient = DefaultRefreshTokenTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
val accessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun passwordAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
val accessTokenResponseClient = DefaultPasswordTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
val accessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun tokenExchangeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
val accessTokenResponseClient = DefaultTokenExchangeTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun restTemplate(): RestTemplate {
// ...
}
}
当没有提供 OAuth2AuthorizedClientManager
bean 时,Spring Security 将自动发布一个默认的。
请注意,在这种情况下,我们不需要自定义 |
在 Spring Security 6.2 之前,我们必须确保此自定义应用于 OAuth2 Login(如果使用此功能)和 OAuth2 Client 组件。我们必须同时使用 Spring Security DSL(用于 authorization_code
授权)并为其他授权类型发布类型为 OAuth2AuthorizedClientManager
的 bean。为了理解幕后配置的内容,配置可能如下所示
RestOperations
(6.2 之前)-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
http
// ...
.oauth2Login((oauth2Login) -> oauth2Login
.tokenEndpoint((tokenEndpoint) -> tokenEndpoint
.accessTokenResponseClient(accessTokenResponseClient)
)
)
.oauth2Client((oauth2Client) -> oauth2Client
.authorizationCodeGrant((authorizationCode) -> authorizationCode
.accessTokenResponseClient(accessTokenResponseClient)
)
);
return http.build();
}
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
DefaultRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient =
new DefaultRefreshTokenTokenResponseClient();
refreshTokenAccessTokenResponseClient.setRestOperations(restTemplate());
DefaultClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate());
DefaultPasswordTokenResponseClient passwordAccessTokenResponseClient =
new DefaultPasswordTokenResponseClient();
passwordAccessTokenResponseClient.setRestOperations(restTemplate());
DefaultJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
new DefaultJwtBearerTokenResponseClient();
jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate());
JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider =
new JwtBearerOAuth2AuthorizedClientProvider();
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient);
DefaultTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient =
new DefaultTokenExchangeTokenResponseClient();
tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate());
TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
new TokenExchangeOAuth2AuthorizedClientProvider();
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient);
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken((refreshToken) -> refreshToken
.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
)
.clientCredentials((clientCredentials) -> clientCredentials
.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
)
.password((password) -> password
.accessTokenResponseClient(passwordAccessTokenResponseClient)
)
.provider(jwtBearerAuthorizedClientProvider)
.provider(tokenExchangeAuthorizedClientProvider)
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
@Bean
public RestTemplate restTemplate() {
// ...
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
tokenResponseClient.setRestOperations(restTemplate())
http {
// ...
oauth2Login {
tokenEndpoint {
accessTokenResponseClient = tokenResponseClient
}
}
oauth2Client {
authorizationCodeGrant {
accessTokenResponseClient = tokenResponseClient
}
}
}
return http.build()
}
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository?,
authorizedClientRepository: OAuth2AuthorizedClientRepository?
): OAuth2AuthorizedClientManager {
val refreshTokenAccessTokenResponseClient = DefaultRefreshTokenTokenResponseClient()
refreshTokenAccessTokenResponseClient.setRestOperations(restTemplate())
val clientCredentialsAccessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate())
val passwordAccessTokenResponseClient = DefaultPasswordTokenResponseClient()
passwordAccessTokenResponseClient.setRestOperations(restTemplate())
val jwtBearerAccessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate())
val jwtBearerAuthorizedClientProvider = JwtBearerOAuth2AuthorizedClientProvider()
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient)
val tokenExchangeAccessTokenResponseClient = DefaultTokenExchangeTokenResponseClient()
tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate())
val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
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 = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository
)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
@Bean
fun restTemplate(): RestTemplate {
// ...
}
}