在将 Spring MVC 与 Webflux 的 ThreadLocal 和 WebClient 一起使用时,我遇到了以下问题。
我的任务是:
拦截用户对我的应用程序的请求,并从中获取所有标头并将其保存在 ThreadLocal 中。
之后,当我的应用程序通过 WebClient 调用另一个服务时,在 ExchangeFilterFunction 中拦截此请求,并用 p.1 中的 Authorization 标头补充它。
当我完成处理用户的请求时,我会清除上下文。
我使用我的自定义类“RequestContext”将标头存储在 ThreadLocal 中:
public class RequestContext {
private HttpHeaders requestHeaders;
private String jwt;
private static final String BEARER_PREFIX = "Bearer ";
public RequestContext(HttpHeaders httpHeaders) {
this.requestHeaders = httpHeaders;
if (Objects.nonNull(httpHeaders)) {
init();
}
}
private void init() {
if (Objects.nonNull(requestHeaders)) {
extractJwt();
}
}
private void extractJwt() {
var jwtHeader = requestHeaders.getFirst(HttpHeaders.AUTHORIZATION);
if (StringUtils.isNotBlank(jwtHeader) && jwtHeader.startsWith(BEARER_PREFIX)) {
jwt = jwtHeader.substring(7);
}
}
}
我使用我的自定义类“RequestContextService”来处理 ThreadLocal:
public class RequestContextService {
private static final ThreadLocal<RequestContext> CONTEXT = new InheritableThreadLocal<>();
public void init(RequestContext requestContext) {
if (Objects.isNull(CONTEXT.get())) {
CONTEXT.set(requestContext);
} else {
log.error("#init: Context init error");
}
}
public RequestContext get() {
return CONTEXT.get();
}
public void clear() {
CONTEXT.remove();
}
}
我的应用是一个 WebMvc 应用。为了完成第 1 步,我使用 HandlerInterceptor 拦截请求并将所有标头设置为 Threadlocal。
public class HeaderInterceptor implements HandlerInterceptor {
private final RequestContextService requestContextService;
@Override
public boolean preHandle(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull Object handler) {
if (Objects.equals(request.getDispatcherType(), DispatcherType.REQUEST)) {
var headers = new ServletServerHttpRequest(request).getHeaders();
requestContextService.init(new RequestContext(headers));
}
return true;
}
@Override
public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
@NonNull Object handler, Exception ex) {
requestContextService.clear();
}
}
如您所见,在每次请求后,我都会调用“requestContextService.clear()”方法来清除 ThreadLocal。
为了执行第二步,我使用了 ExchangeFilterFunction,在这里我转向 threadlocal 并从那里获取标题。
public class SamlExchangeFilterFunction implements ExchangeFilterFunction {
private final RequestContextService requestContextService;
private static final ClientResponse UNAUTHORIZED_CLIENT_RESPONSE =
ClientResponse.create(HttpStatus.UNAUTHORIZED).build();
@Override
public @NotNull Mono<ClientResponse> filter(@NotNull ClientRequest request, @NotNull ExchangeFunction next) {
var jwt = requestContextService.get().getJwt();
if (StringUtils.isNoneBlank(jwt)) {
var clientRequest = ClientRequest.from(request)
.headers(httpHeaders -> httpHeaders.set(SAML_HEADER_NAME, jwt))
.build();
return next.exchange(clientRequest);
}
return Mono.just(UNAUTHORIZED_CLIENT_RESPONSE);
}
}
问题是 SamlExchangeFilterFunction 只能正常工作一次。
在对应用程序的第一次请求时,一切正常。但是对于具有不同授权标头的进一步请求,ExchangeFilterFunction 似乎缓存了第一个请求的值并替换它,尽管 threadlocal 本身包含授权标头的完全不同的含义。