在401响应的情况下,Spring响应式web客户端REST请求带有oauth令牌



我想玩一下Spring响应式web客户端和一个实际简单的例子:请求一个REST资源,在401响应的情况下获得新的OAuth访问令牌。

第一部分似乎很容易:

return webClientBuilder
.baseUrl(targetInstance.getBaseUrl())
.build()
.get().uri(targetInstance.getItemEndpointUrl())
.retrieve()
.bodyToMono(ItemResponse.class)
....

但是在这里混乱已经开始了。我尝试了

.onStatus(HttpStatus::is4xxClientError, (response) -> {
if(response.rawStatusCode() == 401) {
oAuthClient.initToken()

我的令牌应该保存在一个实例JPA实体中。但是我想我在这里缺乏概念上的理解。当OAuth客户端接收到OAuth响应时,我需要首先提取它以在实例实体中持久化它(作为嵌入对象)。因此我需要屏蔽它,对吧?

.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
OAuthResponse oauthResponse = response.bodyToMono(OAuthResponse.class).block();
}

基于OAuth客户端的响应结果,我需要某种Mono来告诉实际的REST客户端,然后如果它应该开始重试?哪种方式应该是首选:.retrieve()还是.exchangeToMono()?所以我在这里有点迷失了,如果我在正确的路径上,或者如果类似的事情应该更好地完成与经典RestTemplate?但是我也读到RestTemplate没有被弃用…

谢谢你和我分享一些想法。

好的,同时我找到了一个非阻塞的方法。也许不是最好的,但对我来说很好。

客户端:

class ApiClient {
public Mono<MyResponse> getResponse(Tenant tenant) {
return webClientBuilder
.baseUrl(tenant.getUrl())
.clientConnector(getClientConnector())
.build()
.get().uri("/api/my-content-entpoint")
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(MyResponse.class);
} else if(response.statusCode().equals(HttpStatus.FORBIDDEN)) {
return Mono.error(new MyOAuthExcpetion());
} else {
return Mono.empty();
}
});
}
}

服务:

@Service
public class MyService {
private final ApiClient apiClient;
private final RetryStrategy retryStrategy;
private final TenantService tenantService;
public Mono<MyResponse> getResponse(String tenantId){
return tenantService.getTenant(tenantId)
.flatMap(tenant-> apiClient.getResponse(instance))
.retryWhen(Retry.from(signals -> signals
.flatMap(retrySignal -> retryStrategy.reconnect(retrySignal, tenantId))));
}
}

和重试策略


@Component
public class RetryStrategy {
private final TenantService tenantService;
public Publisher<? extends Long> reconnect(RetrySignal retrySignal, String tenantId) {

long count = retrySignal.totalRetriesInARow();
Throwable failure = retrySignal.failure();

if(count > 0) {
return Mono.error(new UnsupportedOperationException("Retry failed", failure));
}

Mono<Tenant> updatedTenant = null;

if(failure instanceof MyOAuthExcpetion) {
updatedTenant = tenantService.getTenant(tenantId)
.flatMap(tenant -> tenantService.refreshOAuth(tenant));
}
if(updatedTenant == null) {
return Mono.error(new UnsupportedOperationException("Retry failed", failure));
}

return updatedTenant.then(Mono.delay(Duration.ofSeconds(1))); 
}
}

欢迎任何反馈或改进。

在我的应用程序中,我在发出请求之前预先检查令牌:

client.get()
.uri("...")
.header("Authorization", "Bearer " + authenticator.getToken(client,token))
.retrieve()
...

在Authenticator Service中我验证令牌的有效性,如下所示:

String getToken(WebClient client, String token) {
if (token == null || isTokenExpired(token)) {
return this.fetchToken(client); // fetches a new token
}
return token;
}

private boolean isTokenExpired(String token) {
DecodedJWT jwt = JWT.decode(token);
return jwt.getExpiresAt().before(new Date());
}

最新更新