領先一步
VMware 提供培訓和認證,以加速您的進展。
了解更多在 Spring Security 5 中,我們看到了 OAuth2 相關功能的許多進展,像是將 OAuth2 Resource Server 和 OAuth2 Client 引入框架中。
現在,使用 OAuth2 Resource Server 中提供的功能來開發受 OAuth2 保護的應用程式非常方便。 此外,我們可以利用 OAuth2 Client 功能與 OAuth 2.0 和 OpenID Connect 1.0 提供者整合,從而可以使用 OAuth2 登入來驗證使用者身分,以及/或者向受 OAuth2 保護的應用程式發出受保護的請求。
然而,OAuth2 的情況非常複雜,並且經常需要自訂才能與不夠彈性,甚至不符合各種 OAuth2 相關標準的第三方整合。 考慮到所有這些複雜性,Spring Security 的 OAuth2 Client 元件的開發以極高的靈活性為目標。 這種靈活性帶來了取捨,尤其是在配置方面。
我們聽取了社群關於配置的回饋,而一個常見的主題是簡化各種 OAuth2 Client 元件的配置。 讓我們看看最新的 Spring Security 里程碑版本 6.2.0-M2 中如何簡化配置。
更新: 參考文檔的 OAuth2 頁面已更新,其中包含 OAuth2 Client 的概述,以及基於本文的範例。
讓我們從 start.spring.io 上的一個簡單應用程式開始,我們可以針對可能遇到的各種使用案例進行建構。 以下配置等效於 Spring Boot 提供的預設配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2Client(Customizer.withDefaults())
.oauth2Login(Customizer.withDefaults());
return http.build();
}
}
所需要的只是 application.yml
中的一個 ClientRegistration
,例如以下
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
client-authentication-method: client_secret_basic
scope: openid,profile,message.read,message.write
provider:
my-auth-server:
issuer-uri: https://my-auth-server.com
考慮到以上配置,讓我們考慮以下使用案例
一個常見的使用案例是在取得 access_token
時需要自訂請求參數。 例如,假設我們要將自訂的 audience
參數新增到權杖請求中,因為提供者需要此參數才能使用 authorization_code
授權。
先前,我們必須確保此自訂同時應用於 OAuth2 登入(如果我們正在使用此功能)和使用 Spring Security DSL 的 OAuth2 Client 元件。 以下是配置可能看起來像的樣子
@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()
)
.oauth2Client((oauth2Client) -> oauth2Client
.authorizationCodeGrant((authorizationCode) -> authorizationCode
.accessTokenResponseClient(accessTokenResponseClient)
)
)
.oauth2Login((oauth2Login) -> oauth2Login
.tokenEndpoint((tokenEndpoint) -> tokenEndpoint
.accessTokenResponseClient(accessTokenResponseClient)
)
);
return http.build();
}
private static Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter() {
return (grantRequest) -> {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set("audience", "xyz_value");
return parameters;
};
}
}
在最新的里程碑版本中,我們可以簡單地發布 OAuth2AccessTokenResponseClient<T>
類型的 bean(其中 T
是 OAuth2AuthorizationCodeGrantRequest
),它將被自動選取。 此配置現在可以簡化為
@Configuration
public class SecurityConfig {
@Bean
public DefaultAuthorizationCodeTokenResponseClient 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() {
// ...
}
}
注意: 請注意,因為這是我們執行的唯一自訂,所以實際上我們可以完全省略 SecurityFilterChain
bean,並使用 Spring Boot 提供的預設值。 如果我們需要配置其他內容,情況可能並非總是如此,但無論如何,考慮到我們的配置更簡單,這是值得的。
我們也可以為其他授權類型發布類似的 bean。 例如,若要自訂 client_credentials
授權的權杖請求,我們可以發布以下 bean
@Configuration
public class SecurityConfig {
@Bean
public DefaultClientCredentialsTokenResponseClient 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() {
// ...
}
}
RestOperations
另一個常見的使用案例是需要自訂在取得 access_token
時使用的 RestOperations
(或反應式應用程式的 WebClient
)。 我們可能需要這樣做來客製化回應的處理(透過自訂的 HttpMessageConverter
),或者為公司網路套用 Proxy 設定(透過客製化的 ClientHttpRequestFactory
)。
假設我們想要同時自訂多個授權類型。 先前,我們必須確保此自訂同時應用於 OAuth2 登入(如果我們正在使用此功能)和 OAuth2 Client 元件。 我們必須同時使用 Spring Security DSL(用於 authorization_code
授權)並發布 OAuth2AuthorizedClientManager
類型的 bean 用於其他授權類型,這需要非常冗長的配置。 以下是配置可能看起來像的樣子
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2Client((oauth2Client) -> oauth2Client
.authorizationCodeGrant((authorizationCode) -> authorizationCode
.accessTokenResponseClient(accessTokenResponseClient)
)
)
.oauth2Login((oauth2Login) -> oauth2Login
.tokenEndpoint((tokenEndpoint) -> tokenEndpoint
.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());
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken((refreshToken) -> refreshToken
.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
)
.clientCredentials((clientCredentials) -> clientCredentials
.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
)
.password((password) -> password
.accessTokenResponseClient(passwordAccessTokenResponseClient)
)
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
@Bean
public RestTemplate restTemplate() {
// ...
}
}
在最新的里程碑版本中,我們可以簡單地為每個 OAuth2AccessTokenResponseClient<T>
發布 bean(其中 T
是 Spring Security 中開箱即用的授權類型)。 此配置現在可以簡化為
@Configuration
public class SecurityConfig {
@Bean
public DefaultAuthorizationCodeTokenResponseClient authorizationCodeAccessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public DefaultRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient() {
DefaultRefreshTokenTokenResponseClient accessTokenResponseClient =
new DefaultRefreshTokenTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public DefaultClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient() {
DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public DefaultPasswordTokenResponseClient passwordAccessTokenResponseClient() {
DefaultPasswordTokenResponseClient accessTokenResponseClient =
new DefaultPasswordTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public RestTemplate restTemplate() {
// ...
}
}
事實上,我們甚至可以透過簡單地發布相應的 OAuth2AccessTokenResponseClient
bean 來選擇啟用擴充授權類型 jwt-bearer
@Bean
public DefaultJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient() {
DefaultJwtBearerTokenResponseClient accessTokenResponseClient =
new DefaultJwtBearerTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
注意: 請注意,我們不需要發布 OAuth2AuthorizedClientManager
類型的 bean。 現在 Spring Security 會為我們發布一個。
我們現在可以透過相依性注入使用完全配置的 OAuth2AuthorizedClientManager
,如下所示
@RestController
class MyController {
private final OAuth2AuthorizedClientManager authorizedClientManager;
MyController(OAuth2AuthorizedClientManager authorizedClientManager) {
this.authorizedClientManager = authorizedClientManager;
}
// ...
}
另一個使用案例涉及啟用和/或配置擴充授權類型。 例如,Spring Security 支援 jwt-bearer
授權類型,但預設情況下不會啟用它。
先前,我們必須發布 OAuth2AuthorizedClientManager
類型的 bean,並確保我們重新啟用預設授權類型,這需要一些冗長的配置。 以下是配置可能看起來像的樣子
@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;
}
}
在最新的里程碑版本中,我們可以簡單地為一個或多個 OAuth2AuthorizedClientProvider
發布 bean,它們將被自動選取。 此配置現在可以簡化為
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AuthorizedClientProvider jwtBearer() {
return new JwtBearerOAuth2AuthorizedClientProvider();
}
}
注意: 任何已發布但非由 Spring Security 提供的 OAuth2AuthorizedClientProvider
類型的 bean 也會被選取,並在預設授權類型之後套用。
這也提供了自訂現有授權類型的機會,而無需重新定義預設值。 例如,如果我們想要自訂 client_credentials
授權的 OAuth2AuthorizedClientProvider
的時鐘偏差,我們可以簡單地發布一個如下所示的 bean
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AuthorizedClientProvider clientCredentials() {
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider =
new ClientCredentialsOAuth2AuthorizedClientProvider();
authorizedClientProvider.setClockSkew(Duration.ofMinutes(5));
return authorizedClientProvider;
}
}
我希望您和我一樣對 Spring Security 中透過簡單地發布 @Bean
來配置 OAuth2 Client 元件的簡化方法感到興奮。 如果您想參與其中,請嘗試里程碑版本並向我們提供意見反應! 我們將繼續傾聽並尋找簡化 Spring Security 使用者配置的機會。