OAuth2 WebFlux
Spring Security 提供了全面的 OAuth 2.0 支援。本節討論如何將 OAuth 2.0 整合到您的響應式應用程式中。
概述
Spring Security 的 OAuth 2.0 支援包含兩個主要功能集
|
OAuth2 登入是一個非常強大的 OAuth2 客戶端功能,值得在參考文件中單獨列出一節。但是,它並非獨立功能,需要 OAuth2 客戶端才能執行。 |
這些功能集涵蓋了 OAuth 2.0 授權框架 中定義的資源伺服器和客戶端角色,而授權伺服器角色則由 Spring Authorization Server 涵蓋,這是一個基於 Spring Security 構建的獨立專案。
OAuth2 中的資源伺服器和客戶端角色通常由一個或多個伺服器端應用程式表示。此外,授權伺服器角色可以由一個或多個第三方(如組織內集中身份管理和/或認證的情況)表示,或由一個應用程式(如 Spring Authorization Server 的情況)表示。
例如,一個典型的基於 OAuth2 的微服務架構可能包含一個面向使用者的客戶端應用程式、幾個提供 REST API 的後端資源伺服器以及一個用於管理使用者和認證事宜的第三方授權伺服器。通常情況下,一個應用程式僅代表其中一個角色,需要與提供其他角色的一或多個第三方整合。
Spring Security 處理這些以及更多場景。以下部分涵蓋了 Spring Security 提供的角色,幷包含常見場景的示例。
OAuth2 資源伺服器
|
本節包含 OAuth2 資源伺服器功能的摘要和示例。有關完整的參考文件,請參閱 OAuth 2.0 資源伺服器。 |
首先,將 spring-security-oauth2-resource-server 依賴項新增到您的專案中。使用 Spring Boot 時,新增以下啟動器
-
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 保護對 API 的訪問(授權伺服器提供 JWT 或不透明訪問令牌)
-
我希望 使用 JWT 保護對 API 的訪問(自定義令牌)
使用 OAuth2 訪問令牌保護訪問
使用 OAuth2 訪問令牌保護對 API 的訪問非常常見。在大多數情況下,Spring Security 只需最少的配置即可使用 OAuth2 保護應用程式。
Spring Security 支援兩種型別的 Bearer 令牌,每種令牌都使用不同的元件進行驗證
JWT 支援
以下示例使用 Spring Boot 配置屬性配置 ReactiveJwtDecoder bean
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://my-auth-server.com
使用 Spring Boot 時,只需如此。Spring Boot 提供的預設配置等同於以下配置
-
Java
-
Kotlin
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return ReactiveJwtDecoders.fromIssuerLocation("https://my-auth-server.com");
}
}
import org.springframework.security.config.web.server.invoke
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2ResourceServer {
jwt { }
}
}
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return ReactiveJwtDecoders.fromIssuerLocation("https://my-auth-server.com")
}
}
不透明令牌支援
以下示例使用 Spring Boot 配置屬性配置 ReactiveOpaqueTokenIntrospector bean
spring:
security:
oauth2:
resourceserver:
opaquetoken:
introspection-uri: https://my-auth-server.com/oauth2/introspect
client-id: my-client-id
client-secret: my-client-secret
使用 Spring Boot 時,只需如此。Spring Boot 提供的預設配置等同於以下配置
-
Java
-
Kotlin
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.opaqueToken(Customizer.withDefaults())
);
return http.build();
}
@Bean
public ReactiveOpaqueTokenIntrospector opaqueTokenIntrospector() {
return new SpringReactiveOpaqueTokenIntrospector(
"https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret");
}
}
import org.springframework.security.config.web.server.invoke
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2ResourceServer {
opaqueToken { }
}
}
}
@Bean
fun opaqueTokenIntrospector(): ReactiveOpaqueTokenIntrospector {
return SpringReactiveOpaqueTokenIntrospector(
"https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret"
)
}
}
使用自定義 JWT 保護訪問
使用 JWT 保護對 API 的訪問是一個相當常見的目標,尤其是在前端作為單頁應用程式開發時。Spring Security 中的 OAuth2 資源伺服器支援可用於任何型別的 Bearer 令牌,包括自定義 JWT。
使用 JWT 保護 API 所需的全部功能是 ReactiveJwtDecoder bean,它用於驗證簽名和解碼令牌。Spring Security 將自動使用提供的 bean 在 SecurityWebFilterChain 中配置保護。
以下示例使用 Spring Boot 配置屬性配置 ReactiveJwtDecoder bean
spring:
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:my-public-key.pub
|
您可以將公鑰作為類路徑資源提供(在此示例中稱為 |
使用 Spring Boot 時,只需如此。Spring Boot 提供的預設配置等同於以下配置
-
Java
-
Kotlin
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return NimbusReactiveJwtDecoder.withPublicKey(publicKey()).build();
}
private RSAPublicKey publicKey() {
// ...
}
}
import org.springframework.security.config.web.server.invoke
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2ResourceServer {
jwt { }
}
}
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return NimbusReactiveJwtDecoder.withPublicKey(publicKey()).build()
}
private fun publicKey(): RSAPublicKey {
// ...
}
}
|
Spring Security 不提供用於鑄造令牌的端點。然而,Spring Security 確實提供了 |
OAuth2 客戶端
|
本節包含 OAuth2 客戶端功能的摘要和示例。有關完整的參考文件,請參閱 OAuth 2.0 客戶端 和 OAuth 2.0 登入。 |
首先,將 spring-security-oauth2-client 依賴項新增到您的專案中。使用 Spring Boot 時,新增以下啟動器
-
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 客戶端的以下用例
-
我希望 為使用者獲取訪問令牌 以訪問第三方 API
-
我希望 同時進行(登入使用者並訪問第三方 API)
-
我希望 啟用擴充套件授權型別
-
我希望 自定義現有授權型別
-
我希望 自定義令牌請求引數
使用 OAuth2 登入使用者
要求使用者透過 OAuth2 登入是非常常見的。OpenID Connect 1.0 提供了一個名為 id_token 的特殊令牌,旨在為 OAuth2 客戶端提供執行使用者身份驗證和登入使用者的能力。在某些情況下,OAuth2 可以直接用於登入使用者(例如流行的社交登入提供商,如 GitHub 和 Facebook,它們未實現 OpenID Connect)。
以下示例配置應用程式以充當能夠使用 OAuth2 或 OpenID Connect 登入使用者的 OAuth2 客戶端
-
Java
-
Kotlin
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
// ...
.oauth2Login(Customizer.withDefaults());
return http.build();
}
}
import org.springframework.security.config.web.server.invoke
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
oauth2Login { }
}
}
}
除了上述配置,應用程式還需要透過使用 ReactiveClientRegistrationRepository bean 配置至少一個 ClientRegistration。以下示例使用 Spring Boot 配置屬性配置 InMemoryReactiveClientRegistrationRepository bean
spring:
security:
oauth2:
client:
registration:
my-oidc-client:
provider: my-oidc-provider
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: openid,profile
provider:
my-oidc-provider:
issuer-uri: https://my-oidc-provider.com
透過上述配置,應用程式現在支援兩個額外的端點
-
登入端點(例如
/oauth2/authorization/my-oidc-client)用於啟動登入並重定向到第三方授權伺服器。 -
重定向端點(例如
/login/oauth2/code/my-oidc-client)由授權伺服器用於重定向回客戶端應用程式,並將包含用於透過訪問令牌請求獲取id_token和/或access_token的code引數。
|
上述配置中 |
訪問受保護資源
向受 OAuth2 保護的第三方 API 傳送請求是 OAuth2 客戶端的核心用例。這透過授權客戶端(在 Spring Security 中由 OAuth2AuthorizedClient 類表示)並透過在出站請求的 Authorization 頭中放置 Bearer 令牌來訪問受保護資源實現。
以下示例配置應用程式以充當能夠從第三方 API 請求受保護資源的 OAuth2 客戶端
-
Java
-
Kotlin
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
// ...
.oauth2Client(Customizer.withDefaults());
return http.build();
}
}
import org.springframework.security.config.web.server.invoke
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
oauth2Client { }
}
}
}
|
上述示例並未提供登入使用者的方式。您可以使用任何其他登入機制(例如 |
除了上述配置,應用程式還需要透過使用 ReactiveClientRegistrationRepository bean 配置至少一個 ClientRegistration。以下示例使用 Spring Boot 配置屬性配置 InMemoryReactiveClientRegistrationRepository bean
spring:
security:
oauth2:
client:
registration:
my-oauth2-client:
provider: my-auth-server
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: message.read,message.write
provider:
my-auth-server:
issuer-uri: https://my-auth-server.com
除了配置 Spring Security 以支援 OAuth2 客戶端功能之外,您還需要決定如何訪問受保護資源並相應地配置您的應用程式。Spring Security 提供了 ReactiveOAuth2AuthorizedClientManager 的實現,用於獲取可用於訪問受保護資源的訪問令牌。
|
當不存在預設的 |
使用 ReactiveOAuth2AuthorizedClientManager 最簡單的方法是透過 ExchangeFilterFunction 攔截透過 WebClient 的請求。
以下示例使用預設的 ReactiveOAuth2AuthorizedClientManager 來配置一個 WebClient,該 WebClient 能夠透過在每個請求的 Authorization 頭中放置 Bearer 令牌來訪問受保護資源
ExchangeFilterFunction 配置 WebClient-
Java
-
Kotlin
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction filter =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.filter(filter)
.build();
}
}
@Configuration
class WebClientConfig {
@Bean
fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
val filter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
return WebClient.builder()
.filter(filter)
.build()
}
}
此配置的 WebClient 可按以下示例使用
WebClient 訪問受保護資源-
Java
-
Kotlin
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;
@RestController
public class MessagesController {
private final WebClient webClient;
public MessagesController(WebClient webClient) {
this.webClient = webClient;
}
@GetMapping("/messages")
public Mono<ResponseEntity<List<Message>>> messages() {
return this.webClient.get()
.uri("https://:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.toEntityList(Message.class);
}
public record Message(String message) {
}
}
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId
@RestController
class MessagesController(private val webClient: WebClient) {
@GetMapping("/messages")
fun messages(): Mono<ResponseEntity<List<Message>>> {
return webClient.get()
.uri("https://:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.toEntityList<Message>()
}
data class Message(val message: String)
}
訪問當前使用者的受保護資源
當用戶透過 OAuth2 或 OpenID Connect 登入時,授權伺服器可能會提供一個訪問令牌,該令牌可以直接用於訪問受保護資源。這很方便,因為它只需要同時配置一個 ClientRegistration 即可滿足這兩個用例。
|
本節將使用 OAuth2 登入使用者和訪問受保護資源合併到一個配置中。還存在其他高階場景,例如為登入配置一個 |
以下示例配置應用程式以充當能夠登入使用者並從第三方 API 請求受保護資源的 OAuth2 客戶端
-
Java
-
Kotlin
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
// ...
.oauth2Login(Customizer.withDefaults())
.oauth2Client(Customizer.withDefaults());
return http.build();
}
}
import org.springframework.security.config.web.server.invoke
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
oauth2Login { }
oauth2Client { }
}
}
}
除了上述配置,應用程式還需要透過使用 ReactiveClientRegistrationRepository bean 配置至少一個 ClientRegistration。以下示例使用 Spring Boot 配置屬性配置 InMemoryReactiveClientRegistrationRepository bean
spring:
security:
oauth2:
client:
registration:
my-combined-client:
provider: my-auth-server
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: openid,profile,message.read,message.write
provider:
my-auth-server:
issuer-uri: https://my-auth-server.com
|
與之前的示例(使用 OAuth2 登入使用者、訪問受保護資源)相比,此示例的主要區別在於透過 |
除了配置 Spring Security 以支援 OAuth2 客戶端功能之外,您還需要決定如何訪問受保護資源並相應地配置您的應用程式。Spring Security 提供了 ReactiveOAuth2AuthorizedClientManager 的實現,用於獲取可用於訪問受保護資源的訪問令牌。
|
當不存在預設的 |
使用 ReactiveOAuth2AuthorizedClientManager 最簡單的方法是透過 ExchangeFilterFunction 攔截透過 WebClient 的請求。
以下示例使用預設的 ReactiveOAuth2AuthorizedClientManager 來配置一個 WebClient,該 WebClient 能夠透過在每個請求的 Authorization 頭中放置 Bearer 令牌來訪問受保護資源
ExchangeFilterFunction 配置 WebClient-
Java
-
Kotlin
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction filter =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.filter(filter)
.build();
}
}
@Configuration
class WebClientConfig {
@Bean
fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
val filter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
return WebClient.builder()
.filter(filter)
.build()
}
}
此配置的 WebClient 可按以下示例使用
WebClient 訪問受保護資源(當前使用者)-
Java
-
Kotlin
@RestController
public class MessagesController {
private final WebClient webClient;
public MessagesController(WebClient webClient) {
this.webClient = webClient;
}
@GetMapping("/messages")
public Mono<ResponseEntity<List<Message>>> messages() {
return this.webClient.get()
.uri("https://:8090/messages")
.retrieve()
.toEntityList(Message.class);
}
public record Message(String message) {
}
}
@RestController
class MessagesController(private val webClient: WebClient) {
@GetMapping("/messages")
fun messages(): Mono<ResponseEntity<List<Message>>> {
return webClient.get()
.uri("https://:8090/messages")
.retrieve()
.toEntityList<Message>()
}
data class Message(val message: String)
}
|
與上一個示例不同,請注意我們不需要告訴 Spring Security 我們希望使用的 |
啟用擴充套件授權型別
一個常見的用例是啟用和/或配置擴充套件授權型別。例如,Spring Security 支援 jwt-bearer 和 token-exchange 授權型別,但預設情況下不啟用它們,因為它們不屬於核心 OAuth 2.0 規範。
從 Spring Security 6.3 及更高版本開始,我們只需釋出一個或多個 ReactiveOAuth2AuthorizedClientProvider 的 bean,它們就會被自動拾取。以下示例僅啟用了 jwt-bearer 授權型別
jwt-bearer 授權型別-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public ReactiveOAuth2AuthorizedClientProvider jwtBearer() {
return new JwtBearerReactiveOAuth2AuthorizedClientProvider();
}
}
@Configuration
class SecurityConfig {
@Bean
fun jwtBearer(): ReactiveOAuth2AuthorizedClientProvider {
return JwtBearerReactiveOAuth2AuthorizedClientProvider()
}
}
當未提供 ReactiveOAuth2AuthorizedClientManager bean 時,Spring Security 將自動釋出一個預設的 ReactiveOAuth2AuthorizedClientManager bean。
|
任何自定義的 |
在 Spring Security 6.3 之前,為了實現上述配置,我們必須自己釋出此 bean,並確保也重新啟用預設授權型別。為了理解後臺配置了什麼,配置可能如下所示
jwt-bearer 授權型別(6.3 之前)-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.provider(new JwtBearerReactiveOAuth2AuthorizedClientProvider())
.build();
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
@Configuration
class SecurityConfig {
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ReactiveClientRegistrationRepository,
authorizedClientRepository: ServerOAuth2AuthorizedClientRepository
): ReactiveOAuth2AuthorizedClientManager {
val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.provider(JwtBearerReactiveOAuth2AuthorizedClientProvider())
.build()
val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository
)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
}
自定義現有授權型別
透過釋出 bean 來啟用擴充套件授權型別的能力還提供了自定義現有授權型別的機會,而無需重新定義預設值。例如,如果我們想自定義 client_credentials 授權型別的 ReactiveOAuth2AuthorizedClientProvider 的時鐘偏差,我們只需釋出一個 bean,如下所示
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public ReactiveOAuth2AuthorizedClientProvider clientCredentials() {
ClientCredentialsReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
new ClientCredentialsReactiveOAuth2AuthorizedClientProvider();
authorizedClientProvider.setClockSkew(Duration.ofMinutes(5));
return authorizedClientProvider;
}
}
@Configuration
class SecurityConfig {
@Bean
fun clientCredentials(): ReactiveOAuth2AuthorizedClientProvider {
val authorizedClientProvider = ClientCredentialsReactiveOAuth2AuthorizedClientProvider()
authorizedClientProvider.setClockSkew(Duration.ofMinutes(5))
return authorizedClientProvider
}
}
自定義令牌請求引數
在獲取訪問令牌時自定義請求引數是相當常見的需求。例如,假設我們希望向令牌請求新增一個自定義的 audience 引數,因為提供者要求此引數用於 authorization_code 授權。
我們可以簡單地釋出一個帶有泛型型別 OAuth2AuthorizationCodeGrantRequest 的 ReactiveOAuth2AccessTokenResponseClient 型別的 bean,Spring Security 將使用它來配置 OAuth2 客戶端元件。
以下示例為 authorization_code 授權自定義令牌請求引數
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new WebClientReactiveAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.addParametersConverter(parametersConverter());
return accessTokenResponseClient;
}
private static Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter() {
return (grantRequest) -> {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set("audience", "xyz_value");
return parameters;
};
}
}
@Configuration
class SecurityConfig {
@Bean
fun authorizationCodeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.addParametersConverter(parametersConverter())
return accessTokenResponseClient
}
private fun parametersConverter(): Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> {
return Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> { grantRequest ->
LinkedMultiValueMap<String, String>().also { parameters ->
parameters["audience"] = "xyz_value"
}
}
}
}
|
請注意,在這種情況下我們不需要自定義 |
正如您所看到的,將 ReactiveOAuth2AccessTokenResponseClient 作為 bean 提供非常方便。當直接使用 Spring Security DSL 時,我們需要確保此自定義適用於 OAuth2 登入(如果我們正在使用此功能)和 OAuth2 客戶端元件。為了理解後臺配置了什麼,使用 DSL 的配置可能如下所示
-
Java
-
Kotlin
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new WebClientReactiveAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.addParametersConverter(parametersConverter());
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.oauth2Login((oauth2Login) -> oauth2Login
.authenticationManager(new DelegatingReactiveAuthenticationManager(
new OidcAuthorizationCodeReactiveAuthenticationManager(
accessTokenResponseClient, new OidcReactiveOAuth2UserService()
),
new OAuth2LoginReactiveAuthenticationManager(
accessTokenResponseClient, new DefaultReactiveOAuth2UserService()
)
))
)
.oauth2Client((oauth2Client) -> oauth2Client
.authenticationManager(new OAuth2AuthorizationCodeReactiveAuthenticationManager(
accessTokenResponseClient
))
);
return http.build();
}
private static Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter() {
// ...
}
}
import org.springframework.security.config.web.server.invoke
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.addParametersConverter(parametersConverter())
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2Login {
authenticationManager = DelegatingReactiveAuthenticationManager(
OidcAuthorizationCodeReactiveAuthenticationManager(
accessTokenResponseClient, OidcReactiveOAuth2UserService()
),
OAuth2LoginReactiveAuthenticationManager(
accessTokenResponseClient, DefaultReactiveOAuth2UserService()
)
)
}
oauth2Client {
authenticationManager = OAuth2AuthorizationCodeReactiveAuthenticationManager(
accessTokenResponseClient
)
}
}
}
private fun parametersConverter(): Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> {
// ...
}
}
對於其他授權型別,我們可以釋出額外的 ReactiveOAuth2AccessTokenResponseClient bean 來覆蓋預設值。例如,要為 client_credentials 授權自定義令牌請求,我們可以釋出以下 bean
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
new WebClientReactiveClientCredentialsTokenResponseClient();
accessTokenResponseClient.addParametersConverter(parametersConverter());
return accessTokenResponseClient;
}
private static Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> parametersConverter() {
// ...
}
}
@Configuration
class SecurityConfig {
@Bean
fun clientCredentialsAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
accessTokenResponseClient.addParametersConverter(parametersConverter())
return accessTokenResponseClient
}
private fun parametersConverter(): Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> {
// ...
}
}
Spring Security 自動解析以下 ReactiveOAuth2AccessTokenResponseClient bean 的泛型型別
-
OAuth2AuthorizationCodeGrantRequest(參見WebClientReactiveAuthorizationCodeTokenResponseClient) -
OAuth2RefreshTokenGrantRequest(參見WebClientReactiveRefreshTokenTokenResponseClient) -
OAuth2ClientCredentialsGrantRequest(參見WebClientReactiveClientCredentialsTokenResponseClient) -
JwtBearerGrantRequest(參見WebClientReactiveJwtBearerTokenResponseClient) -
TokenExchangeGrantRequest(參見WebClientReactiveTokenExchangeTokenResponseClient)
|
釋出 |
|
釋出 |
自定義 OAuth2 客戶端元件使用的 WebClient
另一個常見的用例是需要自定義在獲取訪問令牌時使用的 WebClient。我們可能需要這樣做來透過自定義 ClientHttpConnector 自定義底層 HTTP 客戶端庫,以配置 SSL 設定或為公司網路應用代理設定。
從 Spring Security 6.3 及更高版本開始,我們只需釋出 ReactiveOAuth2AccessTokenResponseClient 型別的 bean,Spring Security 將為我們配置併發佈一個 ReactiveOAuth2AuthorizedClientManager bean。
以下示例為所有支援的授權型別自定義 WebClient
WebClient-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new WebClientReactiveAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient());
return accessTokenResponseClient;
}
@Bean
public ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient =
new WebClientReactiveRefreshTokenTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient());
return accessTokenResponseClient;
}
@Bean
public ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
new WebClientReactiveClientCredentialsTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient());
return accessTokenResponseClient;
}
@Bean
public ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
new WebClientReactiveJwtBearerTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient());
return accessTokenResponseClient;
}
@Bean
public ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient =
new WebClientReactiveTokenExchangeTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient());
return accessTokenResponseClient;
}
@Bean
public WebClient webClient() {
// ...
}
}
@Configuration
class SecurityConfig {
@Bean
fun authorizationCodeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient())
return accessTokenResponseClient
}
@Bean
fun refreshTokenAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient())
return accessTokenResponseClient
}
@Bean
fun clientCredentialsAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient())
return accessTokenResponseClient
}
@Bean
fun jwtBearerAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient())
return accessTokenResponseClient
}
@Bean
fun tokenExchangeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient())
return accessTokenResponseClient
}
@Bean
fun webClient(): WebClient {
// ...
}
}
當未提供 ReactiveOAuth2AuthorizedClientManager bean 時,Spring Security 將自動釋出一個預設的 ReactiveOAuth2AuthorizedClientManager bean。
|
請注意,在這種情況下我們不需要自定義 |
在 Spring Security 6.3 之前,我們必須確保將此自定義應用於 OAuth2 客戶端元件。雖然我們可以為 authorization_code 授權釋出 ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> 型別的 bean,但我們必須為其他授權型別釋出 ReactiveOAuth2AuthorizedClientManager 型別的 bean。為了理解後臺配置了什麼,配置可能如下所示
WebClient(6.3 之前)-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new WebClientReactiveAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient());
return accessTokenResponseClient;
}
@Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
WebClientReactiveRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient =
new WebClientReactiveRefreshTokenTokenResponseClient();
refreshTokenAccessTokenResponseClient.setWebClient(webClient());
WebClientReactiveClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient =
new WebClientReactiveClientCredentialsTokenResponseClient();
clientCredentialsAccessTokenResponseClient.setWebClient(webClient());
WebClientReactiveJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
new WebClientReactiveJwtBearerTokenResponseClient();
jwtBearerAccessTokenResponseClient.setWebClient(webClient());
JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider =
new JwtBearerReactiveOAuth2AuthorizedClientProvider();
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient);
WebClientReactiveTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient =
new WebClientReactiveTokenExchangeTokenResponseClient();
tokenExchangeAccessTokenResponseClient.setWebClient(webClient());
TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
new TokenExchangeReactiveOAuth2AuthorizedClientProvider();
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient);
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken((refreshToken) -> refreshToken
.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
)
.clientCredentials((clientCredentials) -> clientCredentials
.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
)
.provider(jwtBearerAuthorizedClientProvider)
.provider(tokenExchangeAuthorizedClientProvider)
.build();
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
@Bean
public WebClient webClient() {
// ...
}
}
import org.springframework.security.config.web.server.invoke
@Configuration
class SecurityConfig {
@Bean
fun authorizationCodeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setWebClient(webClient())
return accessTokenResponseClient
}
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ReactiveClientRegistrationRepository?,
authorizedClientRepository: ServerOAuth2AuthorizedClientRepository?
): ReactiveOAuth2AuthorizedClientManager {
val refreshTokenAccessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
refreshTokenAccessTokenResponseClient.setWebClient(webClient())
val clientCredentialsAccessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
clientCredentialsAccessTokenResponseClient.setWebClient(webClient())
val jwtBearerAccessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
jwtBearerAccessTokenResponseClient.setWebClient(webClient())
val jwtBearerAuthorizedClientProvider = JwtBearerReactiveOAuth2AuthorizedClientProvider()
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient)
val tokenExchangeAccessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
tokenExchangeAccessTokenResponseClient.setWebClient(webClient())
val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider()
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient)
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken { refreshToken ->
refreshToken.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
}
.clientCredentials { clientCredentials ->
clientCredentials.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
}
.provider(jwtBearerAuthorizedClientProvider)
.provider(tokenExchangeAuthorizedClientProvider)
.build()
val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository
)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
@Bean
fun webClient(): WebClient {
// ...
}
}