Webflux中ErrorWebExceptionHandler无法获取登录Principal的问题解决
问题分析与解决方案
为什么仅在控制器/服务中能获取Principal?
Webflux的请求处理基于反应式流,ReactiveSecurityContextHolder的上下文是通过**Mono/Flux的上下文(Context)**传递的:
- 控制器、服务处于正常请求处理流中:从请求进入到业务调用的全链路,所有反应式操作共享同一个上下文链,SecurityContext会自动传递,因此能正常拿到Principal。
- 全局错误处理器的默认执行逻辑:请求抛出异常后,默认错误处理分支可能脱离原始请求的上下文链。如果错误处理器未正确绑定到原始上下文,
ReactiveSecurityContextHolder就无法获取认证信息,返回空上下文。
另外,request.getPrincipal()默认返回Mono.empty(),是因为Webflux Security不会将Principal直接存储在ServerHttpRequest中,而是通过ReactiveSecurityContextHolder的上下文传递,直接调用该方法自然拿不到信息。
实现预期效果的步骤
1. 以反应式方式绑定上下文到错误处理逻辑
不要在错误处理器中同步调用获取Principal的方法,要将获取SecurityContext的操作与错误处理逻辑放在同一个反应式流中,确保上下文传递不中断。
修改GlobalErrorWebExceptionHandler的handle方法,示例代码如下:
import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.security.core.context.SecurityContext; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebExceptionHandler; import reactor.core.publisher.Mono; // 优先级高于默认ErrorWebExceptionHandler(默认@Order(-1)) @Order(-2) @Component public class GlobalErrorWebExceptionHandler implements WebExceptionHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { return ReactiveSecurityContextHolder.getContext() // 提取用户名,无认证则返回"N/A" .map(SecurityContext::getAuthentication) .map(auth -> auth != null ? auth.getName() : "N/A") .defaultIfEmpty("N/A") .flatMap(username -> { // 构建包含用户名的错误响应 ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); String errorJson = String.format( "{\"error_type\":\"%s\",\"username\":\"%s\",\"error_msg\":\"%s\"}", ex.getClass().getSimpleName(), username, ex.getMessage() ); return response.writeWith( Mono.just(response.bufferFactory().wrap(errorJson.getBytes())) ); }) // 处理上下文获取失败的兜底逻辑 .onErrorResume(e -> { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); return response.setComplete(); }); } }
2. 确保错误处理器优先级正确
通过@Order注解设置比默认错误处理器更高的优先级(数值更小,比如@Order(-2)),让你的处理器先执行,保证能捕获到原始请求的上下文。
3. 禁止阻塞操作
错误处理器中绝对不能使用block()这类阻塞式调用,否则会破坏反应式流的上下文传递,导致SecurityContext丢失,所有操作必须保持反应式链式调用。
内容的提问来源于stack exchange,提问作者CptWasp
相关产品推荐
相关产品推荐

