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("https://: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("https://: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("https://: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("https://: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("https://: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("https://: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("https://: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("https://: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 {
// ...
}
}