Spring OAuth2RestTemplate 的安全 5 替换
Spring Security 5 Replacement for OAuth2RestTemplate
在spring-security-oauth2:2.4.0.RELEASE
类中,例如OAuth2RestTemplate
、OAuth2ProtectedResourceDetails
和ClientCredentialsAccessTokenProvider
都被标记为弃用。
从关于这些 类 的 javadoc 中,它指向一个 spring security migration guide,暗示人们应该迁移到核心 spring-security 5 项目。但是,我无法找到如何在此项目中实现我的用例。
如果您希望对应用程序的传入请求进行身份验证并且希望使用第 3 方 OAuth 提供程序来验证身份,则所有文档和示例都会讨论与第 3 部分 OAuth 提供程序集成。
在我的用例中,我只想用 RestTemplate
向受 OAuth 保护的外部服务发出请求。目前,我用我的客户端 ID 和密码创建了一个 OAuth2ProtectedResourceDetails
,并将其传递给 OAuth2RestTemplate
。我还在 OAuth2ResTemplate
中添加了一个自定义 ClientCredentialsAccessTokenProvider
,它只是向我正在使用的 OAuth 提供程序所需的令牌请求添加了一些额外的 headers。
在 spring-security 5 文档中,我找到了一个提到 customising the token request 的部分,但同样看起来是在使用第三方 OAuth 提供程序验证传入请求的上下文中。目前尚不清楚如何将它与 ClientHttpRequestInterceptor
之类的东西结合使用,以确保对外部服务的每个传出请求首先获得一个令牌,然后将其添加到请求中。
在上面链接的迁移指南中也提到了 OAuth2AuthorizedClientService
,它说它对在拦截器中使用很有用,但这看起来又依赖于 ClientRegistrationRepository
之类的东西如果您想使用第三方提供程序来确保传入请求经过身份验证,则可以在它为第三方提供程序维护注册的地方。
有什么方法可以利用 spring-security 5 中的新功能来注册 OAuth 提供程序,以便获取令牌以添加到来自我的应用程序的传出请求中?
Spring Security 5.2.x 的 OAuth 2.0 客户端功能不支持 RestTemplate
,但仅支持 WebClient
。见 Spring Security Reference:
HTTP Client support
WebClient
integration for Servlet Environments (for requesting
protected resources)
此外,RestTemplate
将在未来的版本中弃用。见 RestTemplate javadoc:
NOTE: As of 5.0, the non-blocking, reactive
org.springframework.web.reactive.client.WebClient
offers a modern
alternative to the RestTemplate
with efficient support for both sync
and async, as well as streaming scenarios. The RestTemplate
will be
deprecated in a future version and will not have major new features
added going forward. See the WebClient
section of the Spring Framework
reference documentation for more details and example code.
因此,最好的解决方案是放弃 RestTemplate
,转而使用 WebClient
。
将 WebClient
用于客户端凭据流
以编程方式或使用 Spring 引导自动配置配置客户端注册和提供程序:
spring:
security:
oauth2:
client:
registration:
custom:
client-id: clientId
client-secret: clientSecret
authorization-grant-type: client_credentials
provider:
custom:
token-uri: http://localhost:8081/oauth/token
… 和 OAuth2AuthorizedClientManager
@Bean
:
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
配置 WebClient
实例以使用 ServerOAuth2AuthorizedClientExchangeFilterFunction
和提供的 OAuth2AuthorizedClientManager
:
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("custom");
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
现在,如果您尝试使用此 WebClient
实例发出请求,它将首先从授权服务器请求令牌并将其包含在请求中。
@Anar Sultanov 的上述回答帮助我达到了这一点,但由于我不得不向我的 OAuth 令牌请求添加一些额外的 headers 我想我会提供一个完整的答案来说明我是如何解决这个问题的我的用例问题。
配置提供商详细信息
将以下内容添加到 application.properties
spring.security.oauth2.client.registration.uaa.client-id=${CLIENT_ID:}
spring.security.oauth2.client.registration.uaa.client-secret=${CLIENT_SECRET:}
spring.security.oauth2.client.registration.uaa.scope=${SCOPE:}
spring.security.oauth2.client.registration.uaa.authorization-grant-type=client_credentials
spring.security.oauth2.client.provider.uaa.token-uri=${UAA_URL:}
实施自定义 ReactiveOAuth2AccessTokenResponseClient
由于这是 server-to-server 通信,我们需要使用 ServerOAuth2AuthorizedClientExchangeFilterFunction
。这只接受 ReactiveOAuth2AuthorizedClientManager
,而不接受 non-reactive OAuth2AuthorizedClientManager
。因此,当我们使用 ReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider()
(给它提供者来发出 OAuth2 请求)时,我们必须给它一个 ReactiveOAuth2AuthorizedClientProvider
而不是 non-reactive OAuth2AuthorizedClientProvider
。根据 spring-security reference documentation,如果您使用 non-reactive DefaultClientCredentialsTokenResponseClient
,您可以使用 .setRequestEntityConverter()
方法来更改 OAuth2 令牌请求,但反应等效 WebClientReactiveClientCredentialsTokenResponseClient
不会提供此功能,因此我们必须实现自己的功能(我们可以利用现有的 WebClientReactiveClientCredentialsTokenResponseClient
逻辑)。
我的实现被称为 UaaWebClientReactiveClientCredentialsTokenResponseClient
(省略了实现,因为它只是非常轻微地改变了默认 WebClientReactiveClientCredentialsTokenResponseClient
的 headers()
和 body()
方法以添加一些额外的 headers/body 字段,它不会改变底层的身份验证流程)。
配置WebClient
ServerOAuth2AuthorizedClientExchangeFilterFunction.setClientCredentialsTokenResponseClient()
方法已被弃用,因此请遵循该方法的弃用建议:
Deprecated. Use ServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveOAuth2AuthorizedClientManager)
instead. Create an instance of ClientCredentialsReactiveOAuth2AuthorizedClientProvider
configured with a WebClientReactiveClientCredentialsTokenResponseClient
(or a custom one) and than supply it to DefaultReactiveOAuth2AuthorizedClientManager
.
最后的配置类似于:
@Bean("oAuth2WebClient")
public WebClient oauthFilteredWebClient(final ReactiveClientRegistrationRepository
clientRegistrationRepository)
{
final ClientCredentialsReactiveOAuth2AuthorizedClientProvider
clientCredentialsReactiveOAuth2AuthorizedClientProvider =
new ClientCredentialsReactiveOAuth2AuthorizedClientProvider();
clientCredentialsReactiveOAuth2AuthorizedClientProvider.setAccessTokenResponseClient(
new UaaWebClientReactiveClientCredentialsTokenResponseClient());
final DefaultReactiveOAuth2AuthorizedClientManager defaultReactiveOAuth2AuthorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
defaultReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider(
clientCredentialsReactiveOAuth2AuthorizedClientProvider);
final ServerOAuth2AuthorizedClientExchangeFilterFunction oAuthFilter =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(defaultReactiveOAuth2AuthorizedClientManager);
oAuthFilter.setDefaultClientRegistrationId("uaa");
return WebClient.builder()
.filter(oAuthFilter)
.build();
}
正常使用WebClient
oAuth2WebClient
bean 现在可以用于访问受我们配置的 OAuth2 提供程序保护的资源,就像您使用 WebClient
.
发出任何其他请求一样
我发现@matt Williams 的回答很有帮助。尽管我想添加以防万一有人想以编程方式为 WebClient 配置传递 clientId 和 secret。这是如何完成的。
@Configuration
public class WebClientConfig {
public static final String TEST_REGISTRATION_ID = "test-client";
@Bean
public ReactiveClientRegistrationRepository clientRegistrationRepository() {
var clientRegistration = ClientRegistration.withRegistrationId(TEST_REGISTRATION_ID)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientId("<client_id>")
.clientSecret("<client_secret>")
.tokenUri("<token_uri>")
.build();
return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
}
@Bean
public WebClient testWebClient(ReactiveClientRegistrationRepository clientRegistrationRepo) {
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
oauth.setDefaultClientRegistrationId(TEST_REGISTRATION_ID);
return WebClient.builder()
.baseUrl("https://.test.com")
.filter(oauth)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
}
}
嗨,也许为时已晚,但是 Spring Security 5 仍然支持 RestTemplate,对于非反应性应用程序,RestTemplate 仍在使用您需要做的只是正确配置 spring 安全并创建迁移指南中提到的拦截器
使用如下配置即可使用client_credentials流量
application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: ${okta.oauth2.issuer}/v1/keys
client:
registration:
okta:
client-id: ${okta.oauth2.clientId}
client-secret: ${okta.oauth2.clientSecret}
scope: "custom-scope"
authorization-grant-type: client_credentials
provider: okta
provider:
okta:
authorization-uri: ${okta.oauth2.issuer}/v1/authorize
token-uri: ${okta.oauth2.issuer}/v1/token
OauthResTemplate 的配置
@Configuration
@RequiredArgsConstructor
public class OAuthRestTemplateConfig {
public static final String OAUTH_WEBCLIENT = "OAUTH_WEBCLIENT";
private final RestTemplateBuilder restTemplateBuilder;
private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
private final ClientRegistrationRepository clientRegistrationRepository;
@Bean(OAUTH_WEBCLIENT)
RestTemplate oAuthRestTemplate() {
var clientRegistration = clientRegistrationRepository.findByRegistrationId(Constants.OKTA_AUTH_SERVER_ID);
return restTemplateBuilder
.additionalInterceptors(new OAuthClientCredentialsRestTemplateInterceptorConfig(authorizedClientManager(), clientRegistration))
.setReadTimeout(Duration.ofSeconds(5))
.setConnectTimeout(Duration.ofSeconds(1))
.build();
}
@Bean
OAuth2AuthorizedClientManager authorizedClientManager() {
var authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
拦截器
public class OAuthClientCredentialsRestTemplateInterceptor implements ClientHttpRequestInterceptor {
private final OAuth2AuthorizedClientManager manager;
private final Authentication principal;
private final ClientRegistration clientRegistration;
public OAuthClientCredentialsRestTemplateInterceptor(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
this.manager = manager;
this.clientRegistration = clientRegistration;
this.principal = createPrincipal();
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(principal)
.build();
OAuth2AuthorizedClient client = manager.authorize(oAuth2AuthorizeRequest);
if (isNull(client)) {
throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
}
request.getHeaders().add(HttpHeaders.AUTHORIZATION, BEARER_PREFIX + client.getAccessToken().getTokenValue());
return execution.execute(request, body);
}
private Authentication createPrincipal() {
return new Authentication() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptySet();
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getDetails() {
return null;
}
@Override
public Object getPrincipal() {
return this;
}
@Override
public boolean isAuthenticated() {
return false;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
@Override
public String getName() {
return clientRegistration.getClientId();
}
};
}
}
这将在第一次调用和令牌过期时生成 access_token。 OAuth2AuthorizedClientManager 将为您管理这一切
在spring-security-oauth2:2.4.0.RELEASE
类中,例如OAuth2RestTemplate
、OAuth2ProtectedResourceDetails
和ClientCredentialsAccessTokenProvider
都被标记为弃用。
从关于这些 类 的 javadoc 中,它指向一个 spring security migration guide,暗示人们应该迁移到核心 spring-security 5 项目。但是,我无法找到如何在此项目中实现我的用例。
如果您希望对应用程序的传入请求进行身份验证并且希望使用第 3 方 OAuth 提供程序来验证身份,则所有文档和示例都会讨论与第 3 部分 OAuth 提供程序集成。
在我的用例中,我只想用 RestTemplate
向受 OAuth 保护的外部服务发出请求。目前,我用我的客户端 ID 和密码创建了一个 OAuth2ProtectedResourceDetails
,并将其传递给 OAuth2RestTemplate
。我还在 OAuth2ResTemplate
中添加了一个自定义 ClientCredentialsAccessTokenProvider
,它只是向我正在使用的 OAuth 提供程序所需的令牌请求添加了一些额外的 headers。
在 spring-security 5 文档中,我找到了一个提到 customising the token request 的部分,但同样看起来是在使用第三方 OAuth 提供程序验证传入请求的上下文中。目前尚不清楚如何将它与 ClientHttpRequestInterceptor
之类的东西结合使用,以确保对外部服务的每个传出请求首先获得一个令牌,然后将其添加到请求中。
在上面链接的迁移指南中也提到了 OAuth2AuthorizedClientService
,它说它对在拦截器中使用很有用,但这看起来又依赖于 ClientRegistrationRepository
之类的东西如果您想使用第三方提供程序来确保传入请求经过身份验证,则可以在它为第三方提供程序维护注册的地方。
有什么方法可以利用 spring-security 5 中的新功能来注册 OAuth 提供程序,以便获取令牌以添加到来自我的应用程序的传出请求中?
Spring Security 5.2.x 的 OAuth 2.0 客户端功能不支持 RestTemplate
,但仅支持 WebClient
。见 Spring Security Reference:
HTTP Client support
WebClient
integration for Servlet Environments (for requesting protected resources)
此外,RestTemplate
将在未来的版本中弃用。见 RestTemplate javadoc:
NOTE: As of 5.0, the non-blocking, reactive
org.springframework.web.reactive.client.WebClient
offers a modern alternative to theRestTemplate
with efficient support for both sync and async, as well as streaming scenarios. TheRestTemplate
will be deprecated in a future version and will not have major new features added going forward. See theWebClient
section of the Spring Framework reference documentation for more details and example code.
因此,最好的解决方案是放弃 RestTemplate
,转而使用 WebClient
。
将 WebClient
用于客户端凭据流
以编程方式或使用 Spring 引导自动配置配置客户端注册和提供程序:
spring:
security:
oauth2:
client:
registration:
custom:
client-id: clientId
client-secret: clientSecret
authorization-grant-type: client_credentials
provider:
custom:
token-uri: http://localhost:8081/oauth/token
… 和 OAuth2AuthorizedClientManager
@Bean
:
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
配置 WebClient
实例以使用 ServerOAuth2AuthorizedClientExchangeFilterFunction
和提供的 OAuth2AuthorizedClientManager
:
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("custom");
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
现在,如果您尝试使用此 WebClient
实例发出请求,它将首先从授权服务器请求令牌并将其包含在请求中。
@Anar Sultanov 的上述回答帮助我达到了这一点,但由于我不得不向我的 OAuth 令牌请求添加一些额外的 headers 我想我会提供一个完整的答案来说明我是如何解决这个问题的我的用例问题。
配置提供商详细信息
将以下内容添加到 application.properties
spring.security.oauth2.client.registration.uaa.client-id=${CLIENT_ID:}
spring.security.oauth2.client.registration.uaa.client-secret=${CLIENT_SECRET:}
spring.security.oauth2.client.registration.uaa.scope=${SCOPE:}
spring.security.oauth2.client.registration.uaa.authorization-grant-type=client_credentials
spring.security.oauth2.client.provider.uaa.token-uri=${UAA_URL:}
实施自定义 ReactiveOAuth2AccessTokenResponseClient
由于这是 server-to-server 通信,我们需要使用 ServerOAuth2AuthorizedClientExchangeFilterFunction
。这只接受 ReactiveOAuth2AuthorizedClientManager
,而不接受 non-reactive OAuth2AuthorizedClientManager
。因此,当我们使用 ReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider()
(给它提供者来发出 OAuth2 请求)时,我们必须给它一个 ReactiveOAuth2AuthorizedClientProvider
而不是 non-reactive OAuth2AuthorizedClientProvider
。根据 spring-security reference documentation,如果您使用 non-reactive DefaultClientCredentialsTokenResponseClient
,您可以使用 .setRequestEntityConverter()
方法来更改 OAuth2 令牌请求,但反应等效 WebClientReactiveClientCredentialsTokenResponseClient
不会提供此功能,因此我们必须实现自己的功能(我们可以利用现有的 WebClientReactiveClientCredentialsTokenResponseClient
逻辑)。
我的实现被称为 UaaWebClientReactiveClientCredentialsTokenResponseClient
(省略了实现,因为它只是非常轻微地改变了默认 WebClientReactiveClientCredentialsTokenResponseClient
的 headers()
和 body()
方法以添加一些额外的 headers/body 字段,它不会改变底层的身份验证流程)。
配置WebClient
ServerOAuth2AuthorizedClientExchangeFilterFunction.setClientCredentialsTokenResponseClient()
方法已被弃用,因此请遵循该方法的弃用建议:
Deprecated. Use
ServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveOAuth2AuthorizedClientManager)
instead. Create an instance ofClientCredentialsReactiveOAuth2AuthorizedClientProvider
configured with aWebClientReactiveClientCredentialsTokenResponseClient
(or a custom one) and than supply it toDefaultReactiveOAuth2AuthorizedClientManager
.
最后的配置类似于:
@Bean("oAuth2WebClient")
public WebClient oauthFilteredWebClient(final ReactiveClientRegistrationRepository
clientRegistrationRepository)
{
final ClientCredentialsReactiveOAuth2AuthorizedClientProvider
clientCredentialsReactiveOAuth2AuthorizedClientProvider =
new ClientCredentialsReactiveOAuth2AuthorizedClientProvider();
clientCredentialsReactiveOAuth2AuthorizedClientProvider.setAccessTokenResponseClient(
new UaaWebClientReactiveClientCredentialsTokenResponseClient());
final DefaultReactiveOAuth2AuthorizedClientManager defaultReactiveOAuth2AuthorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
defaultReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider(
clientCredentialsReactiveOAuth2AuthorizedClientProvider);
final ServerOAuth2AuthorizedClientExchangeFilterFunction oAuthFilter =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(defaultReactiveOAuth2AuthorizedClientManager);
oAuthFilter.setDefaultClientRegistrationId("uaa");
return WebClient.builder()
.filter(oAuthFilter)
.build();
}
正常使用WebClient
oAuth2WebClient
bean 现在可以用于访问受我们配置的 OAuth2 提供程序保护的资源,就像您使用 WebClient
.
我发现@matt Williams 的回答很有帮助。尽管我想添加以防万一有人想以编程方式为 WebClient 配置传递 clientId 和 secret。这是如何完成的。
@Configuration
public class WebClientConfig {
public static final String TEST_REGISTRATION_ID = "test-client";
@Bean
public ReactiveClientRegistrationRepository clientRegistrationRepository() {
var clientRegistration = ClientRegistration.withRegistrationId(TEST_REGISTRATION_ID)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientId("<client_id>")
.clientSecret("<client_secret>")
.tokenUri("<token_uri>")
.build();
return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
}
@Bean
public WebClient testWebClient(ReactiveClientRegistrationRepository clientRegistrationRepo) {
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
oauth.setDefaultClientRegistrationId(TEST_REGISTRATION_ID);
return WebClient.builder()
.baseUrl("https://.test.com")
.filter(oauth)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
}
}
嗨,也许为时已晚,但是 Spring Security 5 仍然支持 RestTemplate,对于非反应性应用程序,RestTemplate 仍在使用您需要做的只是正确配置 spring 安全并创建迁移指南中提到的拦截器
使用如下配置即可使用client_credentials流量
application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: ${okta.oauth2.issuer}/v1/keys
client:
registration:
okta:
client-id: ${okta.oauth2.clientId}
client-secret: ${okta.oauth2.clientSecret}
scope: "custom-scope"
authorization-grant-type: client_credentials
provider: okta
provider:
okta:
authorization-uri: ${okta.oauth2.issuer}/v1/authorize
token-uri: ${okta.oauth2.issuer}/v1/token
OauthResTemplate 的配置
@Configuration
@RequiredArgsConstructor
public class OAuthRestTemplateConfig {
public static final String OAUTH_WEBCLIENT = "OAUTH_WEBCLIENT";
private final RestTemplateBuilder restTemplateBuilder;
private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
private final ClientRegistrationRepository clientRegistrationRepository;
@Bean(OAUTH_WEBCLIENT)
RestTemplate oAuthRestTemplate() {
var clientRegistration = clientRegistrationRepository.findByRegistrationId(Constants.OKTA_AUTH_SERVER_ID);
return restTemplateBuilder
.additionalInterceptors(new OAuthClientCredentialsRestTemplateInterceptorConfig(authorizedClientManager(), clientRegistration))
.setReadTimeout(Duration.ofSeconds(5))
.setConnectTimeout(Duration.ofSeconds(1))
.build();
}
@Bean
OAuth2AuthorizedClientManager authorizedClientManager() {
var authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
拦截器
public class OAuthClientCredentialsRestTemplateInterceptor implements ClientHttpRequestInterceptor {
private final OAuth2AuthorizedClientManager manager;
private final Authentication principal;
private final ClientRegistration clientRegistration;
public OAuthClientCredentialsRestTemplateInterceptor(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
this.manager = manager;
this.clientRegistration = clientRegistration;
this.principal = createPrincipal();
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(principal)
.build();
OAuth2AuthorizedClient client = manager.authorize(oAuth2AuthorizeRequest);
if (isNull(client)) {
throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
}
request.getHeaders().add(HttpHeaders.AUTHORIZATION, BEARER_PREFIX + client.getAccessToken().getTokenValue());
return execution.execute(request, body);
}
private Authentication createPrincipal() {
return new Authentication() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptySet();
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getDetails() {
return null;
}
@Override
public Object getPrincipal() {
return this;
}
@Override
public boolean isAuthenticated() {
return false;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
@Override
public String getName() {
return clientRegistration.getClientId();
}
};
}
}
这将在第一次调用和令牌过期时生成 access_token。 OAuth2AuthorizedClientManager 将为您管理这一切