提取 Mono 非阻塞响应并将其存储在变量中并全局使用

Extract Mono nonblocking response and store it in a variable and use it globally

在我的项目中,我有一个要求,我需要调用第三方 api 真实 url 来获取访问令牌。我需要在每个后续请求中设置该访问令牌 header。访问令牌有一定的生命周期,当生命周期到期时,我需要重新生成访问令牌。

application.yml 我已经对 client_id、client_secret、auth_url 和 grant_type 进行了硬编码。

AuthController.java 我在这里创建了一个端点来生成访问令牌。

**`AuthService.java`**
@Services
@Slf4j
public class AuthService{
 @Autowired
 private WebClient webClient;
static String accessToken="";
public Mono<SeekResponse> getAccessToken(AuthRequest authRequest) throws InvalidTokenException{
  Mono<AuthResponse> authResponse=webClient.post()
                  .bodyValue(authRequest)
                  .accept(MediaType.APPLICATION_JSON)
                  .retrive()
                  .bodyToMono(AuthResponse.class);

 authResponse.doOnNext(response->{
       String value=response.getAccess_token();
       accessToken=accessToken+value; 
  })
}
}

虽然我已经更新了“accessToken”值,但它 return 将我设为空。我明白,因为我已经进行了异步调用,所以这个值为 null。我不能在这里使用阻塞机制。 有没有其他方法可以生成访问令牌并将其作为 header 传递给后续的身份验证请求。或者我如何在全局范围内使用 accessToken 值,以便我可以将这些令牌值设置为我随后的 api 请求调用。

我已按照以下文章尝试使用 oAuth2: https://medium.com/@asce4s/oauth2-with-spring-webclient-761d16f89cdd 但是当我执行时出现以下错误: “找不到预期的 CSRF 令牌”。

提供的示例和实现不是真正的反应式,很难理解。方法 returns Mono 但同时 throws InvalidTokenExceptiononNext 的用法是 so-called side-effect 应该用于日志记录的操作、指标或其他类似用例。

您为 WebClient 实现 oauth 流程的方式是创建 filterClient Filters.

Spring Security 为常见的 oauth 流程提供了一些样板。查看 Spring Security OAuth2 了解更多详情。

这里是客户端凭证提供程序的简单实现示例

private ServerOAuth2AuthorizedClientExchangeFilterFunction oauth(String clientRegistrationId, ClientConfig config) {
    var clientRegistration = ClientRegistration
            .withRegistrationId(clientRegistrationId)
            .tokenUri(config.getAuthUrl() + "/token")
            .clientId(config.getClientId())
            .clientSecret(config.getClientSecret())
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .build();

    var authRepository = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
    var authClientService = new InMemoryReactiveOAuth2AuthorizedClientService(authRepository);

    var authClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
            authRepository, authClientService);

    var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authClientManager);
    oauth.setDefaultClientRegistrationId(clientRegistrationId);
    return oauth;
}

那么你可以在WebClient

中使用它
WebClient.builder()
    .filter(oauth)
    .build()

更新 这是没有过滤器的替代方法的示例

AuthService

@Service
public class AuthService {
    private final WebClient webClient;

    public AuthService() {
        this.webClient = WebClient.create("<url>/token");
    }

    public Mono<String> getAccessToken() {
        return webClient.post()
                .bodyValue()
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(AuthResponse.class)
                .map(res -> res.getAccessToken());
    }
}

ApiService

@Service
public class ApiService {
    private final WebClient webClient;
    private final Mono<String> requestToken;

    public ApiService(AuthService authService) {
        this.webClient = WebClient.create("<url>/api");
        // cache for token expiration
        this.requestToken = authService.getAccessToken().cache(Duration.ofMinutes(10));
    }

    public Mono<String> request() {
        return requestToken
                .flatMap(token ->
                        webClient.get()
                                .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
                                .accept(MediaType.APPLICATION_JSON)
                                .retrieve()
                                .bodyToMono(String.class)
                );
    }
}

我也在学习 Webflux。这是我的想法。如有不妥请指正

我们不会依赖 doOnNext()doOnSuccess() 或其他类似的方法来尝试处理 pre-defined 变量 accessToken(这不是让 Mono 流动)。我们应该关注的是将一个单声道转换为另一个单声道,例如将单声道响应转换为单声道访问令牌。

方法是 .flatmap()/.map()/.zipwith()/... 例如,

Mono<string> tokenMono = responseMono.flatmap(
    // in the map or flatmap, we get the chance to operate on  variables/objects.
    resp -> { 
        string token = response.getAccess_token();
        return Mono.just(token); // with Mono.just(), we are able to convert object to Mono again.
    }
) // this example is not practical, as map() is better to do the same thing. flatmap with Mono.just() is meaningless here.

Mono<string> tokenMono2 = responseMono.map(
        resp -> { 
            string token = response.getAccess_token();
            return token;
        }
    ) 

从 Mono 开始的所有内容都应该始终是 Mono,直到被订阅或阻止。它们为我们提供了对 Mono<variables> 中的那些 variables 进行操作的方法。这些是 map() flatmap()zipwith()

参考作者说的一点,doOnNext()是为了日志等副作用。