OAuth2

Spring Security 提供全面的 OAuth 2.0 支援。本節討論如何將 OAuth 2.0 整合到您的基於 Servlet 的應用程式中。

概述

Spring Security 的 OAuth 2.0 支援包含三個主要功能集

OAuth2 登入是一個非常強大的 OAuth2 客戶端功能,值得在參考文件中單獨列出一節。但是,它並非獨立功能,需要 OAuth2 客戶端才能執行。

這些功能集涵蓋了 OAuth 2.0 授權框架中定義的 資源伺服器客戶端授權伺服器 角色。

OAuth2 中的 資源伺服器客戶端 角色通常由一個或多個伺服器端應用程式表示。此外,授權伺服器 角色可以由一個或多個第三方表示(例如,當在組織內集中身份管理和/或身份驗證時) -或- 可以由一個應用程式表示(例如,使用 授權伺服器 功能時)。

例如,一個典型的基於 OAuth2 的微服務架構可能包含一個面向使用者的客戶端應用程式,幾個提供 REST API 的後端資源伺服器,以及一個用於管理使用者和身份驗證的第三方授權伺服器。單個應用程式只扮演其中一個角色,而需要與提供其他角色的一方或多方第三方整合的情況也很常見。

Spring Security 處理這些場景以及更多。以下部分將介紹 Spring Security 提供的角色,幷包含常見場景的示例。

OAuth2 資源伺服器

本節包含 OAuth2 資源伺服器功能的摘要和示例。請參閱OAuth 2.0 資源伺服器以獲取完整的參考文件。

首先,將 spring-security-oauth2-resource-server 依賴新增到您的專案中。使用 Spring Boot 時,新增以下啟動器

使用 Spring Boot 的 OAuth2 客戶端
  • 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 資源伺服器用例

使用 OAuth2 訪問令牌保護訪問

使用 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 提供的預設安排等效於以下內容

使用 JWT 配置資源伺服器
  • 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 資源伺服器支援可用於任何型別的 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

您可以將公鑰作為類路徑資源提供(在此示例中稱為 my-public-key.pub)。

使用 Spring Boot 時,這就是所有必需的。Spring Boot 提供的預設安排等效於以下內容

使用自定義 JWT 配置資源伺服器
  • 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 確實提供了 JwtEncoder 介面以及一個實現,即 NimbusJwtEncoder

OAuth2 客戶端

本節包含 OAuth2 客戶端功能的摘要和示例。請參閱OAuth 2.0 客戶端OAuth 2.0 登入以獲取完整的參考文件。

首先,將 spring-security-oauth2-client 依賴項新增到您的專案中。使用 Spring Boot 時,新增以下啟動器

使用 Spring Boot 的 OAuth2 客戶端
  • 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 提供了一個名為 id_token 的特殊令牌,它旨在為 OAuth2 客戶端提供執行使用者身份驗證和登入使用者的能力。在某些情況下,OAuth2 可以直接用於登入使用者(例如,GitHub 和 Facebook 等不實現 OpenID Connect 的流行社交登入提供商)。

以下示例配置應用程式作為 OAuth2 客戶端,能夠使用 OAuth2 或 OpenID Connect 登入使用者

配置 OAuth2 登入
  • 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()
	}

}

除了上述配置,應用程式還需要透過使用 ClientRegistrationRepository bean 配置至少一個 ClientRegistration。以下示例使用 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

透過上述配置,應用程式現在支援兩個額外的端點

  1. 登入端點(例如 /oauth2/authorization/my-oidc-client)用於發起登入並重定向到第三方授權伺服器。

  2. 重定向端點(例如 /login/oauth2/code/my-oidc-client)由授權伺服器用於重定向回客戶端應用程式,並將包含一個 code 引數,該引數用於透過訪問令牌請求獲取 id_token 和/或 access_token

上述配置中 openid 作用域的存在表示應使用 OpenID Connect 1.0。這會指示 Spring Security 在請求處理期間使用 OIDC 特定的元件(例如 OidcUserService)。如果沒有此作用域,Spring Security 將轉而使用 OAuth2 特定的元件(例如 DefaultOAuth2UserService)。

訪問受保護資源

向受 OAuth2 保護的第三方 API 傳送請求是 OAuth2 客戶端的核心用例。這透過授權客戶端(在 Spring Security 中由 OAuth2AuthorizedClient 類表示)並透過在出站請求的 Authorization 標頭中放置 Bearer 令牌來訪問受保護資源來實現。

以下示例配置應用程式作為 OAuth2 客戶端,能夠從第三方 API 請求受保護資源

配置 OAuth2 客戶端
  • 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()
	}

}

上面的示例沒有提供登入使用者的方法。您可以使用任何其他登入機制(例如 formLogin())。有關結合使用 oauth2Client()oauth2Login() 的示例,請參閱下一節

除了上述配置,應用程式還需要透過使用 ClientRegistrationRepository bean 配置至少一個 ClientRegistration。以下示例使用 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 客戶端功能外,您還需要決定如何訪問受保護資源並相應地配置您的應用程式。Spring Security 提供了 OAuth2AuthorizedClientManager 的實現,用於獲取可用於訪問受保護資源的訪問令牌。

OAuth2AuthorizedClientManager bean 不存在時,Spring Security 會為您註冊一個預設的 OAuth2AuthorizedClientManager bean。

使用 OAuth2AuthorizedClientManager 最簡單的方法是透過 ClientHttpRequestInterceptor,它透過 RestClient 攔截請求,當 spring-web 在類路徑上時,它已經可用。

以下示例使用預設的 OAuth2AuthorizedClientManager 來配置 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 客戶端的核心用例。這透過授權客戶端(在 Spring Security 中由 OAuth2AuthorizedClient 類表示)並透過在出站請求的 Authorization 標頭中放置 Bearer 令牌來訪問受保護資源來實現。

以下示例配置應用程式作為 OAuth2 客戶端,能夠從第三方 API 請求受保護資源

配置 OAuth2 客戶端
  • 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()
	}

}

上面的示例沒有提供登入使用者的方法。您可以使用任何其他登入機制(例如 formLogin())。有關結合使用 oauth2Client()oauth2Login() 的示例,請參閱上一節

除了上述配置,應用程式還需要透過使用 ClientRegistrationRepository bean 配置至少一個 ClientRegistration。以下示例使用 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 客戶端功能外,您還需要決定如何訪問受保護資源並相應地配置您的應用程式。Spring Security 提供了 OAuth2AuthorizedClientManager 的實現,用於獲取可用於訪問受保護資源的訪問令牌。

OAuth2AuthorizedClientManager bean 不存在時,Spring Security 會為您註冊一個預設的 OAuth2AuthorizedClientManager bean。

除了配置 RestClient 之外,另一種使用 OAuth2AuthorizedClientManager 的方法是透過 ExchangeFilterFunction,它透過 WebClient 攔截請求。要使用 WebClient,您需要新增 spring-webflux 依賴項以及一個響應式客戶端實現

新增 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,它能夠透過在每個請求的 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 登入使用者訪問受保護資源結合到一個配置中。還存在其他高階場景,例如為登入配置一個 ClientRegistration,為訪問受保護資源配置另一個 ClientRegistration。所有這些場景都將使用相同的基本配置。

以下示例配置應用程式作為 OAuth2 客戶端,能夠登入使用者 從第三方 API 請求受保護資源

配置 OAuth2 登入和 OAuth2 客戶端
  • 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()
	}

}

除了上述配置,應用程式還需要透過使用 ClientRegistrationRepository bean 配置至少一個 ClientRegistration。以下示例使用 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 登入使用者訪問受保護資源)與此示例的主要區別在於透過 scope 屬性配置的內容,它結合了標準範圍 openidprofile 以及自定義範圍 message.readmessage.write

除了配置 Spring Security 以支援 OAuth2 客戶端功能外,您還需要決定如何訪問受保護資源並相應地配置您的應用程式。Spring Security 提供了 OAuth2AuthorizedClientManager 的實現,用於獲取可用於訪問受保護資源的訪問令牌。

OAuth2AuthorizedClientManager bean 不存在時,Spring Security 會為您註冊一個預設的 OAuth2AuthorizedClientManager bean。

使用 OAuth2AuthorizedClientManager 最簡單的方法是透過 ClientHttpRequestInterceptor,它透過 RestClient 攔截請求,當 spring-web 在類路徑上時,它已經可用。

以下示例使用預設的 OAuth2AuthorizedClientManager 來配置 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 我們想使用的 clientRegistrationId。這是因為它可以通過當前登入的使用者派生出來。

使用客戶端憑據授權

本節重點介紹客戶端憑據授權型別的其他注意事項。有關所有授權型別的通用設定和用法,請參閱訪問受保護資源

客戶端憑據授權允許客戶端代表自己獲取 access_token。客戶端憑據授權是一個簡單的流程,不涉及資源所有者(即使用者)。

需要注意的是,客戶端憑據授權的典型使用意味著任何請求(或使用者)都可能獲取訪問令牌並向資源伺服器發出受保護資源請求。在設計應用程式時請務必謹慎,以確保使用者無法發出未經授權的請求,因為每個請求都將能夠獲取訪問令牌。

在使用者可以登入的 Web 應用程式中獲取訪問令牌時,Spring Security 的預設行為是為每個使用者獲取一個訪問令牌。

預設情況下,訪問令牌的作用域是當前使用者的主體名稱,這意味著每個使用者將收到一個唯一的訪問令牌。

使用客戶端憑據授權的客戶端通常要求訪問令牌的作用域是應用程式而不是單個使用者,因此每個應用程式只有一個訪問令牌。為了將訪問令牌的作用域限定為應用程式,您需要設定一個解析自定義主體名稱的策略。以下示例透過使用 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()
	}

}

透過上述配置,可以為每個請求指定一個主體名稱。以下示例演示瞭如何透過指定主體名稱將訪問令牌的作用域限定為應用程式

將訪問令牌的作用域限定為應用程式
  • 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)

}

當透過屬性指定主體名稱(如上例所示)時,將只有一個訪問令牌,並且它將用於所有請求。

啟用擴充套件授權型別

一個常見的用例是啟用和/或配置擴充套件授權型別。例如,Spring Security 支援 jwt-bearertoken-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 將自動釋出一個預設的 OAuth2AuthorizedClientManager bean。

任何自定義的 OAuth2AuthorizedClientProvider bean 也將被拾取並應用於提供的 OAuth2AuthorizedClientManager,在預設授權型別之後。

在 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()
				.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()
			.provider(JwtBearerOAuth2AuthorizedClientProvider())
			.build()

		val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
			clientRegistrationRepository, authorizedClientRepository
		)
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

		return authorizedClientManager
	}

}

自定義現有授權型別

透過釋出 bean 來啟用擴充套件授權型別的能力還提供了自定義現有授權型別的機會,而無需重新定義預設值。例如,如果我們要自定義 client_credentials 授權型別的 OAuth2AuthorizedClientProvider 的時鐘偏差,我們可以簡單地釋出一個 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 及更高版本開始,我們可以簡單地釋出一個泛型型別為 OAuth2AuthorizationCodeGrantRequestOAuth2AccessTokenResponseClient 型別 bean,Spring Security 將使用它來配置 OAuth2 客戶端元件。

以下示例在不使用 DSL 的情況下為 authorization_code 授權自定義令牌請求引數

為授權碼授權自定義令牌請求引數
  • Java

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
		RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new RestClientAuthorizationCodeTokenResponseClient();
		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(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val accessTokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
		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"
			}
		}
	}

}

請注意,在這種情況下,我們不需要自定義 SecurityFilterChain bean,並且可以堅持使用預設值。如果使用 Spring Boot 且沒有額外的自定義,我們實際上可以完全省略 SecurityFilterChain bean。

在 Spring Security 6.2 之前,我們必須確保此自定義應用於 OAuth2 登入(如果使用此功能)和使用 Spring Security DSL 的 OAuth2 客戶端元件。為了理解幕後配置的內容,以下是配置可能的樣子

為授權碼授權自定義令牌請求引數(6.2 之前)
  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new RestClientAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.addParametersConverter(parametersConverter());

		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 tokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
		tokenResponseClient.addParametersConverter(parametersConverter())

		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() {
		RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
				new RestClientClientCredentialsTokenResponseClient();
		accessTokenResponseClient.addParametersConverter(parametersConverter());

		return accessTokenResponseClient;
	}

	private static Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> parametersConverter() {
		// ...
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
		val accessTokenResponseClient = RestClientClientCredentialsTokenResponseClient()
		accessTokenResponseClient.addParametersConverter(parametersConverter())

		return accessTokenResponseClient
	}

	private fun parametersConverter(): Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> {
		// ...
	}

}

Spring Security 自動解析 OAuth2AccessTokenResponseClient bean 的以下泛型型別

  • OAuth2AuthorizationCodeGrantRequest(參見 RestClientAuthorizationCodeTokenResponseClient

  • OAuth2RefreshTokenGrantRequest(參見 RestClientRefreshTokenTokenResponseClient

  • OAuth2ClientCredentialsGrantRequest(參見 RestClientClientCredentialsTokenResponseClient

  • JwtBearerGrantRequest(參見 RestClientJwtBearerTokenResponseClient

  • TokenExchangeGrantRequest(參見 RestClientTokenExchangeTokenResponseClient

釋出 OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> 型別的 bean 將自動啟用 jwt-bearer 授權型別,而無需單獨配置它

釋出 OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> 型別的 bean 將自動啟用 token-exchange 授權型別,而無需單獨配置它

自定義 OAuth2 客戶端元件使用的 RestClient

另一個常見的用例是需要自定義獲取訪問令牌時使用的 RestClient。我們可能需要這樣做來定製響應的處理(透過自定義 HttpMessageConverter)或為公司網路應用代理設定(透過自定義 ClientHttpRequestFactory)。

從 Spring Security 6.2 及更高版本開始,我們只需釋出 OAuth2AccessTokenResponseClient 型別的 bean,Spring Security 將為我們配置併發佈一個 OAuth2AuthorizedClientManager bean。

以下示例為所有支援的授權型別自定義 RestClient

為 OAuth2 客戶端自定義 RestClient
  • Java

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
		RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new RestClientAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.setRestClient(restClient());

		return accessTokenResponseClient;
	}

	@Bean
	public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
		RestClientRefreshTokenTokenResponseClient accessTokenResponseClient =
			new RestClientRefreshTokenTokenResponseClient();
		accessTokenResponseClient.setRestClient(restClient());

		return accessTokenResponseClient;
	}

	@Bean
	public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
		RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
			new RestClientClientCredentialsTokenResponseClient();
		accessTokenResponseClient.setRestClient(restClient());

		return accessTokenResponseClient;
	}

	@Bean
	public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
		RestClientJwtBearerTokenResponseClient accessTokenResponseClient =
			new RestClientJwtBearerTokenResponseClient();
		accessTokenResponseClient.setRestClient(restClient());

		return accessTokenResponseClient;
	}

	@Bean
	public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
		RestClientTokenExchangeTokenResponseClient accessTokenResponseClient =
			new RestClientTokenExchangeTokenResponseClient();
		accessTokenResponseClient.setRestClient(restClient());

		return accessTokenResponseClient;
	}

	@Bean
	public RestClient restClient() {
		// ...
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val accessTokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.setRestClient(restClient())

		return accessTokenResponseClient
	}

	@Bean
	fun refreshTokenAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
		val accessTokenResponseClient = RestClientRefreshTokenTokenResponseClient()
		accessTokenResponseClient.setRestClient(restClient())

		return accessTokenResponseClient
	}

	@Bean
	fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
		val accessTokenResponseClient = RestClientClientCredentialsTokenResponseClient()
		accessTokenResponseClient.setRestClient(restClient())

		return accessTokenResponseClient
	}

	@Bean
	fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
		val accessTokenResponseClient = RestClientJwtBearerTokenResponseClient()
		accessTokenResponseClient.setRestClient(restClient())

		return accessTokenResponseClient
	}

	@Bean
	fun tokenExchangeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
		val accessTokenResponseClient = RestClientTokenExchangeTokenResponseClient()
		accessTokenResponseClient.setRestClient(restClient())

		return accessTokenResponseClient
	}

	@Bean
	fun restClient(): RestClient {
		// ...
	}

}

當未提供 OAuth2AuthorizedClientManager bean 時,Spring Security 將自動釋出一個預設的 OAuth2AuthorizedClientManager bean。

請注意,在這種情況下,我們不需要自定義 SecurityFilterChain bean,並且可以堅持使用預設值。如果使用 Spring Boot 且沒有額外的自定義,我們實際上可以完全省略 SecurityFilterChain bean。

在 Spring Security 6.2 之前,我們必須確保此自定義應用於 OAuth2 登入(如果使用此功能)和 OAuth2 客戶端元件。我們必須同時使用 Spring Security DSL(用於 authorization_code 授權)併為其他授權型別釋出 OAuth2AuthorizedClientManager 型別的 bean。為了理解幕後配置的內容,以下是配置可能的樣子

為 OAuth2 客戶端自定義 RestClient(6.2 之前)
  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new RestClientAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.setRestClient(restClient());

		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) {

		RestClientRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient =
			new RestClientRefreshTokenTokenResponseClient();
		refreshTokenAccessTokenResponseClient.setRestClient(restClient());

		RestClientClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient =
			new RestClientClientCredentialsTokenResponseClient();
		clientCredentialsAccessTokenResponseClient.setRestClient(restClient());

		RestClientJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
			new RestClientJwtBearerTokenResponseClient();
		jwtBearerAccessTokenResponseClient.setRestClient(restClient());

		JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider =
			new JwtBearerOAuth2AuthorizedClientProvider();
		jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient);

		RestClientTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient =
			new RestClientTokenExchangeTokenResponseClient();
		tokenExchangeAccessTokenResponseClient.setRestClient(restClient());

		TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
			new TokenExchangeOAuth2AuthorizedClientProvider();
		tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient);

		OAuth2AuthorizedClientProvider authorizedClientProvider =
			OAuth2AuthorizedClientProviderBuilder.builder()
				.authorizationCode()
				.refreshToken((refreshToken) -> refreshToken
					.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
				)
				.clientCredentials((clientCredentials) -> clientCredentials
					.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
				)
				.provider(jwtBearerAuthorizedClientProvider)
				.provider(tokenExchangeAuthorizedClientProvider)
				.build();

		DefaultOAuth2AuthorizedClientManager authorizedClientManager =
			new DefaultOAuth2AuthorizedClientManager(
				clientRegistrationRepository, authorizedClientRepository);
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

		return authorizedClientManager;
	}

	@Bean
	public RestClient restClient() {
		// ...
	}

}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		val tokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
		tokenResponseClient.setRestClient(restClient())

		http {
			// ...
			oauth2Login {
				tokenEndpoint {
					accessTokenResponseClient = tokenResponseClient
				}
			}
			oauth2Client {
				authorizationCodeGrant {
					accessTokenResponseClient = tokenResponseClient
				}
			}
		}

		return http.build()
	}

	@Bean
	fun authorizedClientManager(
		clientRegistrationRepository: ClientRegistrationRepository?,
		authorizedClientRepository: OAuth2AuthorizedClientRepository?
	): OAuth2AuthorizedClientManager {
		val refreshTokenAccessTokenResponseClient = RestClientRefreshTokenTokenResponseClient()
		refreshTokenAccessTokenResponseClient.setRestClient(restClient())

		val clientCredentialsAccessTokenResponseClient = RestClientClientCredentialsTokenResponseClient()
		clientCredentialsAccessTokenResponseClient.setRestClient(restClient())

		val jwtBearerAccessTokenResponseClient = RestClientJwtBearerTokenResponseClient()
		jwtBearerAccessTokenResponseClient.setRestClient(restClient())

		val jwtBearerAuthorizedClientProvider = JwtBearerOAuth2AuthorizedClientProvider()
		jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient)

		val tokenExchangeAccessTokenResponseClient = RestClientTokenExchangeTokenResponseClient()
		tokenExchangeAccessTokenResponseClient.setRestClient(restClient())

		val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
		tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient)

		val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
			.authorizationCode()
			.refreshToken { refreshToken ->
				refreshToken.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
			}
			.clientCredentials { clientCredentials ->
				clientCredentials.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
			}
			.provider(jwtBearerAuthorizedClientProvider)
			.provider(tokenExchangeAuthorizedClientProvider)
			.build()

		val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
			clientRegistrationRepository, authorizedClientRepository
		)
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

		return authorizedClientManager
	}

	@Bean
	fun restClient(): RestClient {
		// ...
	}

}

進一步閱讀

前面的章節介紹了 Spring Security 對 OAuth2 的支援,並提供了常見場景的示例。您可以在參考文件的以下章節中閱讀更多關於 OAuth2 客戶端、資源伺服器和授權伺服器的內容

© . This site is unofficial and not affiliated with VMware.