如何用Mono中的JWT增强非过时版ReactiveElasticsearchClient请求
为Spring Data Elasticsearch ELC客户端添加动态JWT授权头的非阻塞方案
由于ELC包(org.springframework.data.elasticsearch.client.elc)基于Apache HttpAsyncClient,无法直接像ERHLC那样用WebClient过滤器处理非阻塞的JWT获取,推荐使用客户端装饰器模式,在每个请求链路中动态从ReactiveSecurityContextHolder获取JWT并修改请求头,具体步骤如下:
1. 实现带认证的ReactiveElasticsearchClient包装类
创建一个装饰器类,委托原生客户端的方法,同时在执行请求前插入JWT头的获取逻辑:
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.http.HttpHeaders; import org.elasticsearch.client.RequestOptions; import reactor.core.publisher.Mono; @Component public class AuthenticatedReactiveElasticsearchClient implements ReactiveElasticsearchClient { private final ReactiveElasticsearchClient delegate; public AuthenticatedReactiveElasticsearchClient(ReactiveElasticsearchClient delegate) { this.delegate = delegate; } @Override public <T> Mono<T> execute(ClientCallback<Mono<T>> callback) { return ReactiveSecurityContextHolder.getContext() // 从SecurityContext中提取JWT令牌 .map(ctx -> (JwtAuthenticationToken) ctx.getAuthentication()) .map(JwtAuthenticationToken::getToken) .map(jwt -> "Bearer " + jwt.getTokenValue()) // 处理未认证的情况 .defaultIfEmpty("") .flatMap(authHeader -> { // 构建带授权头的RequestOptions RequestOptions authOptions = RequestOptions.DEFAULT.toBuilder() .addHeader(HttpHeaders.AUTHORIZATION, authHeader) .build(); // 用自定义RequestOptions执行请求 return callback.execute(new ClientConfiguration() { @Override public RequestOptions toRequestOptions() { return authOptions; } @Override public HttpHeaders getHeaders() { return delegate.getConfiguration().getHeaders(); } // 其他ClientConfiguration方法按需委托或实现默认逻辑 }); }) // 兜底:如果SecurityContext为空,用原配置执行请求 .switchIfEmpty(callback.execute(delegate.getConfiguration())); } // 委托其他客户端方法(如indices、search等) @Override public ReactiveIndicesClient indices() { return delegate.indices(); } @Override public <T> ReactiveSearchClient<T> search(Class<T> clazz) { return delegate.search(clazz); } // 覆盖其他必要方法,均委托给原生客户端即可 }
2. 配置包装类为主要客户端Bean
在配置类中,将自定义的包装类注册为ReactiveElasticsearchClient的Bean,替代原生实例:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient; @Configuration public class ElasticsearchAuthConfig { @Bean public ReactiveElasticsearchClient authenticatedReactiveElasticsearchClient(ReactiveElasticsearchClient nativeClient) { return new AuthenticatedReactiveElasticsearchClient(nativeClient); } }
方案说明
- 该方案完全基于Reactor非阻塞模型,通过
ReactiveSecurityContextHolder.getContext()异步获取JWT,不会阻塞线程 - 利用装饰器模式,无需修改原生客户端的底层配置,对业务代码无侵入
- 处理了未认证场景的兜底逻辑,避免请求因缺少头而失败
- 所有请求都会动态生成带授权头的
RequestOptions,保证每个请求的头都是当前上下文的有效JWT
内容的提问来源于stack exchange,提问作者GeBeater
相关产品推荐
相关产品推荐

