首页 > 解决方案 > 使用 webclient 在微服务之间中继令牌

问题描述

我有两个微服务,我们将它们命名为 A 和 B,位于 Spring Cloud Gateway 后面。还有一个 OAuthServer。当用户向服务 A 发送请求时,他们必须首先使用密码流从 oAuthServer 获取 JWT 令牌。

需要注意的是,服务 A 需要与服务 B 通信,但为​​此它需要一个访问令牌。这个想法是在访问服务 A 时使用用户提供的 JWT,通过将其中继到服务 B 从而发出请求。

在以前版本的 Spring Boot 中我会使用OAuth2RestTemplate,但现在我需要使用Webclient.

我所做的是创建一个过滤器以从服务 A 中的传入请求中提取承载令牌,然后将其存储在单例类中并手动将其添加到出站调用中。

@Component
public class JWTAuthorizationFilter extends OncePerRequestFilter {

private static final Logger log = LoggerFactory.getLogger(JWTAuthorizationFilter.class);

private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";

private TokenStore tokenStore;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws ServletException, IOException {

    if (checkJWTToken(request, response)) {
        String token = extractToken(request);
        if (token != null) {
            log.info("*****RECEIVED REQUEST WITH TOKEN {}. ", token);
            log.info("***** Will store it for future requests.");
            tokenStore = TokenStore.getInstance();
            tokenStore.setToken(token);
            // setUpSpringAuthentication(token);
        } else {
            SecurityContextHolder.clearContext();
        }
    }
    chain.doFilter(request, response);

}

private String extractToken(HttpServletRequest request) {
    String jwtToken = request.getHeader(HEADER).replace(PREFIX, "");
    return jwtToken;
}

private boolean checkJWTToken(HttpServletRequest request, HttpServletResponse res) {
    String authenticationHeader = request.getHeader(HEADER);
    if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX))
        return false;
    return true;
}

TokenStore

public class TokenStore {

private static TokenStore instance;

private String token;

private TokenStore() {
}

public static synchronized TokenStore getInstance() {
    if (instance == null) {
        instance = new TokenStore();
    }
    return instance;
}

public String getToken() {
    return token;
}

public synchronized void setToken(String token) {
    this.token = token;
}

}

最后是网络客户端:

@Bean
@LoadBalanced
@Profile("!default")
public WebClient.Builder loadBalancedWebClientBuilder() {
    final WebClient.Builder builder = WebClient.builder();
    return builder;
}

使用它:

Mono<Map<Long, DrawDTO>> mono = getWebClient().get().uri(url)
            .header("Authorization", "Bearer " + tokenStore.getToken()).retrieve().bodyToFlux(DTO.class)
            .map(d -> Tuples.of(Long.parseLong(d.getUniqueId()), d))
            .collectMap(tuple -> tuple.getT1(), tuple -> tuple.getT2()).log()
            .onErrorMap(WebClientResponseException.class, ex -> handleException(ex)).timeout(Duration.ofSeconds(5));

但是,我很确定以上是一个错误。如果两个请求同时来自用户XY ,则 TokenStore 是一个单例,第二个过滤器调用将覆盖第一个的令牌。所以本质上服务 A 将使用用户的Y令牌来完成用户X的请求。因此,如果用户的X访问级别低于Y,这是一个安全问题。

我对上述内容有误吗?如果是,我该怎么做才能解决它?

我发现的一种解决方案是使用客户端凭据进行服务内通信,但我不能 100% 确定如何使用 Spring Boot 2.2.6 和 Spring Security 5 来做到这一点。

有没有正确的方法来做我最初打算做的事情?

标签: spring-bootoauth-2.0microservicesspring-webclient

解决方案


推荐阅读