搶先一步
VMware 提供培訓和認證,以加速您的進度。
了解更多在 Spring Security 6.2 和 6.3 中,我們致力於穩定地改進使用 OAuth2 Client 的應用程式的配置。通過允許應用程式發佈在應用程式啟動期間自動包含在整體 OAuth2 Client 配置中的 Bean,簡化了常見用例的配置。最近的改進包括
OAuth2AuthorizedClientProvider
(或 ReactiveOAuth2AuthorizedClientProvider
)類型的 Bean 即可啟用擴展授權類型OAuth2AccessTokenResponseClient
(或 ReactiveOAuth2AccessTokenResponseClient
)類型的 Bean,即可使用自訂參數擴展 OAuth 2.0 存取權杖請求OAuth2AuthorizedClientManager
(或 ReactiveOAuth2AuthorizedClientManager
)類型的 Bean,從而減少了應用程式需要獲取存取權杖時的樣板程式碼配置在 Spring Security 6.4 中,這個主題通過一系列針對 RestClient
的改進而延續,RestClient
是 Spring Framework 6.1 中引入的一個新的 HTTP 客戶端。RestClient
提供了一個流暢的 API,它與 WebClient
的 API 非常相似,但它是同步的,並且不依賴於反應式程式庫。這意味著配置一個使用 OAuth2 Client 發出受保護資源請求的應用程式要簡單得多,並且不需要任何額外的依賴項。此外,還進行了改進,以確保使用 RestClient
的 Servlet 應用程式與使用 WebClient
的反應式應用程式之間的一致性,目標是在一個通用的配置模型上對齊兩個堆疊。
讓我們詳細研究一下對 RestClient
的新支援以及 OAuth2 Client 的其他改進。
首先,讓我們總結一下我們將使用的 OAuth2 中的相關概念。
用 OAuth2 的術語來說,發出*受保護的資源請求*意味著在發送到*資源伺服器*的出站請求的 Authorization
標頭中包含存取權杖。原始應用程式被稱為*客戶端*,因為它啟動了這些出站請求。目標應用程式被稱為*資源伺服器*,因為它提供了一個 API 來存取屬於*資源所有者*(例如,使用者)並且受到*授權伺服器*保護的*資源*(例如,資料)。*授權伺服器*是一個負責建立和管理代表*授權許可*的存取權杖的系統,它根據*客戶端*代表*資源所有者*發出的請求(稱為 OAuth 2.0 存取權杖請求)來執行此操作。
RestClient
發出受保護的資源請求有了這個簡短的介紹,讓我們看看如何在 Spring Security 6.4 中使用 RestClient
設定應用程式以發出受保護的資源請求。前往 Spring Initializr 建立一個新的應用程式。如果您正在使用 Spring Boot 更新現有的應用程式,則需要新增以下依賴項
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
該應用程式需要至少一個通過使用 ClientRegistrationRepository
Bean 配置的 ClientRegistration
。ClientRegistration
類別是 Spring Security 中的域模型,其中包含特定 OAuth2 客戶端的資料。每個客戶端都必須預先在授權伺服器上註冊,並且此類別包含從授權伺服器獲取的詳細資訊,例如 clientId
和 clientSecret
。它還包含我們想要使用的 authorizationGrantType
,例如 authorization_code
或 client_credentials
,以及可以根據需要選擇性配置的幾個其他參數。
以下範例使用 Spring Boot 配置屬性配置具有單個 ClientRegistration
的 InMemoryClientRegistrationRepository
Bean
application.yml
:
spring:
security:
oauth2:
client:
registration:
messaging-client:
provider: spring
client-id: client1
client-secret: my-secret
authorization-grant-type: authorization_code
scope: message.read,message.write
provider:
spring:
issuer-uri: https://127.0.0.1:9000
上述配置允許 Spring Security 使用 本機授權伺服器通過 authorization_code
授權獲得存取權杖。
Spring Security 提供了 OAuth2AuthorizedClientManager
的實作,它是一個可用於獲取存取權杖(例如 JWT)的元件。Spring Security 會自動將此元件的實例作為 Bean 發佈,這意味著我們只需要將它注入到我們自己的配置中,以便設定一個 RestClient
來在我們的應用程式中發出受保護的資源請求。以下範例配置了一個最小的 RestClient
並將其作為 Bean 發佈
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(RestClient.Builder builder, OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
return builder.requestInterceptor(requestInterceptor).build();
}
}
我們現在可以在我們自己的應用程式中發出受保護的資源請求。以下範例示範如何在 Spring MVC 控制器中執行此操作
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://127.0.0.1:8090/messages")
.attributes(clientRegistrationId("messaging-client"))
.retrieve()
.body(Message[].class);
return ResponseEntity.ok(Arrays.asList(messages));
}
public record Message(String message) {
}
}
上面的範例使用了一個靜態方法,通過屬性將 "messaging-client"
的 registrationId
提供給攔截器。提供的值與先前提供的 yaml 配置中的值相符,Spring Security 就是通過這種方式知道在獲取存取權杖時要使用哪個客戶端 ID、密碼、授權類型、範圍和其他資訊。
當然,這只是一個範例,您不限於僅在端點中返回結果。您可以在應用程式的任何部分執行此操作,例如負責發出受保護的資源請求並將結果返回到您的應用程式的 @Service
或 @Component
。
RestClient
發出 OAuth 2.0 存取權杖請求在 Spring Security 6.4 之前,Servlet 堆疊的預設 HTTP 客戶端是 RestTemplate
。由於 RestTemplate
和 WebClient
之間的 API 差異,使用 RestTemplate
自訂 Servlet 應用程式的 OAuth 2.0 存取權杖請求與自訂使用 WebClient
的反應式應用程式非常不同。
隨著 Spring Framework 6.1 中引入了 RestClient
,現在可以通過分別使用 RestClient
和 WebClient
作為每個堆疊的底層 HTTP 客戶端,使用非常相似的配置模型對齊兩個堆疊。如果需要,可以使用 RestClient.create(RestTemplate)
從 RestTemplate
建立 RestClient
,從而為在一個通用配置模型上對齊 Servlet 和反應式堆疊提供了一個清晰的遷移路徑,這是 Spring Security 7 的目標。
Spring Security 6.4 為此引入了新的 OAuth2AccessTokenResponseClient
實作。如果需要,您可以選擇在 Servlet 應用程式中將 RestClient
用作所有 OAuth2 Client 功能的 HTTP 客戶端。以下範例示範了選擇使用新支援和自訂 RestClient
實例的最小配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final RestClient restClient;
@PostConstruct
void initialize() {
this.restClient = RestClient.builder()
.messageConverters((messageConverters) -> {
messageConverters.clear();
messageConverters.add(new FormHttpMessageConverter());
messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
})
.defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
// TODO: Customize the instance of RestClient as needed...
.build();
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new RestClientAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestClient(this.restClient);
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
RestClientRefreshTokenTokenResponseClient accessTokenResponseClient =
new RestClientRefreshTokenTokenResponseClient();
accessTokenResponseClient.setRestClient(this.restClient);
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
new RestClientClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRestClient(this.restClient);
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
return (grantRequest) -> {
throw new UnsupportedOperationException("The `password` grant type is not supported.");
};
}
@Bean
public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
RestClientJwtBearerTokenResponseClient accessTokenResponseClient =
new RestClientJwtBearerTokenResponseClient();
accessTokenResponseClient.setRestClient(this.restClient);
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
RestClientTokenExchangeTokenResponseClient accessTokenResponseClient =
new RestClientTokenExchangeTokenResponseClient();
accessTokenResponseClient.setRestClient(this.restClient);
return accessTokenResponseClient;
}
}
注意:新支援沒有
password
授權類型的實作,因為對此授權類型的現有支援已棄用,並計劃在 Spring Security 7 中刪除。
Spring Security 通過 OAuth2AccessTokenResponseClient
(或 ReactiveOAuth2AccessTokenResponseClient
)介面的實作支援多種授權類型。一個常見的要求是能夠自訂 OAuth 2.0 存取權杖請求的參數,這在授權伺服器有特定要求或提供受支援規範中未涵蓋的功能時很常見。
在 Spring Security 6.3 及更早版本中,反應式應用程式無法覆寫或省略 Spring Security 設定的參數值,因此需要使用一些變通方法才能針對特定使用情境自訂應用程式。現在,反應式應用程式(使用 WebClient
)和 servlet 應用程式(使用 RestClient
)都可以透過 setParametersConverter()
自訂鉤子來覆寫參數。在此情況下,請務必注意,所有授權類型特定參數和預設參數都會先設定。您的自訂 parametersConverter
提供的任何參數都會覆寫現有參數。
除了覆寫參數之外,現在還可以省略可能被授權伺服器拒絕的參數。例如,當 ClientRegistration#clientAuthenticationMethod
設定為 private_key_jwt
時,我們可以透過包含產生的 JWT 的客戶端斷言來提供客戶端身份驗證。某些授權伺服器可能會選擇拒絕同時包含 client_id
和 client_assertion
參數的請求。在這種情況下,由於 client_id
是 Spring Security 提供的預設參數,因此我們需要一種方法,根據我們將使用客戶端斷言提供客戶端身份驗證的知識來省略此參數。
Spring Security 6.4 提供了使用 setParametersCustomizer()
自訂鉤子來省略 OAuth 2.0 存取令牌請求的參數的功能。以下範例示範了在使用客戶端斷言進行客戶端身份驗證且授權類型為 client_credentials
時如何省略 client_id
參數
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
new WebClientReactiveClientCredentialsTokenResponseClient();
accessTokenResponseClient.addParametersConverter(
new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver()));
accessTokenResponseClient.setParametersCustomizer((parameters) -> {
if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
parameters.remove(OAuth2ParameterNames.CLIENT_ID);
}
});
return accessTokenResponseClient;
}
private Function<ClientRegistration, JWK> jwkResolver() {
// ...
}
}
提示:當使用
RestClientClientCredentialsTokenResponseClient
(或其他授權類型的替代實作)時,您也可以為 servlet 應用程式提供等效的組態。
Spring Security 6.4 是一個令人振奮的版本,其中包含許多針對使用 OAuth2 保護的應用程式的改進,並且還包含許多其他令人興奮的功能。在這篇文章中,我們檢視了即將發布版本中的三個新功能。首先,我們討論了在非反應式應用程式中使用 RestClient
發出受保護資源請求,而無需額外的依賴項。接下來,我們研究了選擇在所有地方使用 RestClient
,並享受簡化且更一致的組態,使其與反應式堆疊對齊。最後,我們學習了如何在 OAuth 2.0 存取令牌請求中覆寫或省略預設參數,這解鎖了以前難以考慮的進階情境。
我希望您和我一樣對這一輪的改進以及 Spring Security 6.4 提供的所有其他功能感到興奮。這些功能以及更多功能可在 Spring Security 6.4.0-RC1
的預發布版本中使用,因此請試用一下。我們很樂意聽到您的回饋!