领先一步
VMware 提供培训和认证,助您快速提升进度。
了解更多在 Spring Security 5 中,我们看到了 OAuth2 故事的许多发展,包括将 OAuth2 资源服务器和 OAuth2 客户端引入框架。
如今,使用 OAuth2 资源服务器中提供的功能开发受 OAuth2 保护的应用程序非常方便。此外,我们可以利用 OAuth2 客户端功能与 OAuth 2.0 和 OpenID Connect 1.0 提供程序集成,从而可以使用 OAuth2 登录对用户进行身份验证和/或向受 OAuth2 保护的应用程序发出受保护的请求。
但是,OAuth2 领域非常复杂,通常需要进行自定义才能与第三方集成,这些第三方具有不灵活甚至不符合各种 OAuth2 相关标准的实现。面对所有这些复杂性,Spring Security 的 OAuth2 客户端组件的设计考虑了极大的灵活性。这种灵活性也带来了权衡,尤其是在配置方面。
我们听取了社区关于配置的反馈,一个共同的主题是简化各种 OAuth2 客户端组件的配置。让我们看看在最新的 Spring Security 里程碑版本 6.2.0-M2 中如何简化配置。
更新:参考文档的 OAuth2 页面已更新,其中包含 OAuth2 客户端 的概述以及基于本文的示例。
让我们从 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
授权模式下需要此参数。
以前,我们必须确保使用 Spring Security DSL 对 OAuth2 登录(如果我们正在使用此功能)和 OAuth2 客户端组件都应用此自定义。以下是配置可能的样子
@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
)或为公司网络应用代理设置(通过自定义的 ClientHttpRequestFactory
)。
假设我们想要同时自定义多个授权模式。以前,我们必须确保此自定义应用于 OAuth2 登录(如果我们正在使用此功能)和 OAuth2 客户端组件。我们必须同时使用 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;
}
}
我希望您和我一样对通过发布 @Bean
来简化配置 Spring Security 中 OAuth2 客户端组件的方法感到兴奋。如果您想参与其中,请尝试使用里程碑版本并 向我们提供反馈!我们将继续倾听并寻找简化 Spring Security 用户配置的机会。