首页 > 解决方案 > 在 401 响应的情况下,带有 oauth 令牌的 Spring 响应式 Web 客户端 REST 请求

问题描述

我想尝试使用 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 没有被弃用......

感谢您与我分享一些想法。

标签: spring-webflux

解决方案


好的,与此同时,我找到了一种非阻塞方式。也许不是最好的,但对我来说效果很好。

客户端:

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))); 
    }
}

很高兴有任何反馈或改进。


推荐阅读