我必须使用一个外部API,它使用OAuth2作为安全性。它们不支持授予类型"client_credentials",而是提供了一个长期存在的refresh_token,我们可以将其注入到Spring应用程序中,而不需要它过期。
但是,我找不到关于如何为OAuth2设置WebClient过滤器的任何信息--使用给定的refresh_token获取access_token。
下面是一些代码,以获得一个起点,并说明我在哪里尝试过。
@Value("application.external-api.refresh_token")
private String refresh_token;
@Bean
public WebClient externalApiWebClient(
ReactiveClientRegistrationRepository clientRegistrations,
ServerOAuth2AuthorizedClientRepository authorizedClients
) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
oauth.setDefaultClientRegistrationId("external-api");
// TODO: Somehow get the refresh_token into the OAuth filter
return WebClient.builder()
.baseUrl("https://api.external-api.com")
.filter(oauth)
.build();
}spring:
security:
oauth2:
client:
registration:
external-api:
# client-secret: <refresh_token>
authorization-grant-type: refresh_token
provider:
external-api:
token-uri: https://api.external-api.com/oauth2/token有人需要做一些类似的事情并找到解决这个问题的方法吗?
发布于 2022-09-15 21:53:35
是的,我做过这样的事。我所处的环境是(试图)与Google智能设备管理生态系统集成。我不确定我是否推荐它,但我至少成功地使用了刷新令牌连接到API。
我可以想出两种方法来实现这一点:
ReactiveOAuth2AuthorizedClientService类refreshToken这两种方法都是相似的,因为无论在哪种情况下,我们都没有accessToken,必须给框架一个过期的令牌,所以它知道在第一次使用时加载一个新的令牌。
我选择了第二种选择,尽管事后看来,第一种选择可能会更好。
下面是一个类,它将所有概念联系在一起,并允许您为基于请求的访问或不响应用户请求的后台服务(例如计划的批处理作业)创建一个WebClient:
@Component
public class WebClientFactory {
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
private final ReactiveOAuth2AuthorizedClientService authorizedClientService;
private final ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider;
private final String baseUrl;
public WebClientFactory(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository,
ReactiveOAuth2AuthorizedClientService authorizedClientService,
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider,
@Value("${my.base-url}") String baseUrl) {
this.clientRegistrationRepository = clientRegistrationRepository;
this.authorizedClientRepository = authorizedClientRepository;
this.authorizedClientService = authorizedClientService;
this.authorizedClientProvider = authorizedClientProvider;
this.baseUrl = baseUrl;
}
public Mono<Void> saveRefreshToken(String registrationId, String principalName, String token) {
var now = Instant.now();
var accessToken = new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER, "none", now.minusSeconds(2), now.minusSeconds(1));
var refreshToken = new OAuth2RefreshToken(token, now);
var principal = new BearerTokenAuthenticationToken(principalName);
principal.setAuthenticated(true);
return this.clientRegistrationRepository.findByRegistrationId(registrationId)
.switchIfEmpty(Mono.error(new InternalAuthenticationServiceException("Unable to find registrationId " + registrationId)))
.map((clientRegistration) -> new OAuth2AuthorizedClient(clientRegistration, principalName, accessToken, refreshToken))
.flatMap((authorizedClient) -> this.authorizedClientService.saveAuthorizedClient(authorizedClient, principal));
}
public WebClient createRequestClient() {
var authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
this.clientRegistrationRepository, this.authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(this.authorizedClientProvider);
return createWebClient(authorizedClientManager);
}
public WebClient createBackgroundClient() {
var authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
this.clientRegistrationRepository, this.authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(this.authorizedClientProvider);
return createWebClient(authorizedClientManager);
}
private WebClient createWebClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
var oauth2 = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.filter(oauth2)
.baseUrl(this.baseUrl)
.build();
}
}注意:为了简单起见,我正在从spring-security-oauth2-resource-server重用BearerTokenAuthenticationToken,因为需要某种类型的主体来唯一地标识刷新令牌。
Spring将为您提供大部分注入bean(默认为内存中的),但您需要提供ReactiveOAuth2AuthorizedClientProvider。
@Bean
public ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider() {
return ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.refreshToken()
.build();
}如果在后台服务中使用BearerTokenAuthenticationToken,则需要自己通过ReactiveSecurityContextHolder设置与principal相同的WebClient。
有关更多信息,请参见文档。
https://stackoverflow.com/questions/73733680
复制相似问题