Spring Boot Webflux中ThreadLocal上下文清理时机与调用位置咨询
Webflux下ThreadLocal上下文脏数据问题修复方案
问题根因
- 你当前的Filter仅在请求处理前调用了
SecurityAuthorizationContext.create()存入ThreadLocal,没有在请求结束后做清理,Spring Boot线程池复用线程时会携带上次请求遗留的上下文,触发重复初始化异常 - 额外注意:Webflux为异步响应式架构,请求处理过程会发生线程切换,ThreadLocal仅绑定当前线程,切换线程后会出现上下文丢失问题,本质并不适配Webflux场景
快速修复方案(仅解决脏数据问题,不解决线程切换丢失问题)
直接修改AuthorizationFilter的filter方法,通过Mono的doFinally钩子保证无论请求成功、异常、取消,都会清理当前线程的ThreadLocal:
@Override public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) { ServerHttpRequest request = serverWebExchange.getRequest(); HttpHeaders headers = request.getHeaders(); List<String> roles = headers.get(USER_ROLES); SecurityAuthorizationContext.create(roles); // 添加上下文清理逻辑 return webFilterChain.filter(serverWebExchange) .doFinally(signalType -> SecurityAuthorizationContext.clean()); }
Webflux场景下的最优方案(解决全链路上下文传递问题)
替换ThreadLocal为Reactor自带的ContextView存储上下文,上下文会绑定到响应流,跟随流的全链路传递,不受线程切换影响,也不存在线程复用脏数据问题:
第一步:改造SecurityAuthorizationContext
public class SecurityAuthorizationContext { // 定义Context存取的key public static final Class<PrivilegeHolder> PRIVILEGE_HOLDER_KEY = PrivilegeHolder.class; public static Mono<PrivilegeHolder> create(List<String> roles) { return Mono.fromCallable(() -> { PrivilegeHolder privilegeHolder = new PrivilegeHolder(); // 示例:根据用户角色从数据库查询权限 privilegeHolder.add(INSERT); privilegeHolder.add(DELETE); return privilegeHolder; }); } // 从Reactor上下文获取权限对象 public static Mono<PrivilegeHolder> getContext() { return Mono.deferContextual(contextView -> Mono.just(contextView.get(PRIVILEGE_HOLDER_KEY))); } public static Mono<Boolean> hasInsertPrivilege() { return getContext().map(privilegeHolder -> privilegeHolder.hasPrivilege(INSERT)); } public static Mono<Boolean> hasDeletePrivilege() { return getContext().map(privilegeHolder -> privilegeHolder.hasPrivilege(DELETE)); } }
第二步:改造AuthorizationFilter,将权限对象写入响应流上下文
@Override public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) { ServerHttpRequest request = serverWebExchange.getRequest(); HttpHeaders headers = request.getHeaders(); List<String> roles = headers.get(USER_ROLES); // 构造权限对象后写入流上下文 return SecurityAuthorizationContext.create(roles) .flatMap(privilegeHolder -> webFilterChain.filter(serverWebExchange) .contextWrite(context -> context.put(SecurityAuthorizationContext.PRIVILEGE_HOLDER_KEY, privilegeHolder))); }
第三步:业务层使用示例
无需参数传递,直接调用对应方法即可获取上下文:
// 校验插入权限示例 public Mono<Void> addData(Data data) { return SecurityAuthorizationContext.hasInsertPrivilege() .filter(hasPrivilege -> hasPrivilege) .switchIfEmpty(Mono.error(new AuthorizationException("无插入权限"))) .then(/* 业务逻辑 */); }
内容的提问来源于stack exchange,提问作者VeryNiceArgumentException
相关产品推荐
相关产品推荐

