You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从ServerWebExchange获取Principal并写入MDC用于日志记录?

在Spring Reactive中从ServerWebExchange获取Principal字符串并存入MDC

在Reactive环境下不能直接阻塞获取Mono<Principal>的值,必须遵循响应式编程规则处理流,下面是可行的实现方案:

核心实现代码

@Component
public class RequestTraceFilter implements WebFilter {

    private static final String REQUEST_ID = "request-id";
    private static final String USER_NAME = "user-name";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        // 生成随机请求ID并放入MDC
        String requestId = UUID.randomUUID().toString();
        MDC.put(REQUEST_ID, requestId);

        // 处理Principal,提取用户名存入MDC
        return exchange.getPrincipal()
                // 从Principal中提取用户名
                .map(Principal::getName)
                // 未认证请求默认使用anonymous
                .defaultIfEmpty("anonymous")
                // 当Mono成功发射用户名时,写入MDC
                .doOnNext(userName -> MDC.put(USER_NAME, userName))
                // 继续执行后续过滤链
                .then(chain.filter(exchange))
                // 流结束后清理MDC,避免线程复用导致的脏数据
                .doFinally(signalType -> {
                    MDC.remove(REQUEST_ID);
                    MDC.remove(USER_NAME);
                });
    }
}

关键注意事项

  1. 绝对不要用block()获取Mono值
    Reactive环境下调用block()会阻塞线程,破坏非阻塞设计,甚至引发线程池死锁,必须用map/doOnNext这类响应式操作符处理流。

  2. 处理未认证场景
    用defaultIfEmpty("anonymous")处理未登录用户的请求,避免Mono为空时后续逻辑中断。

  3. 必须清理MDC
    WebFlux使用线程池复用线程,doFinally会在流结束(成功/失败/取消)时执行,确保MDC里的请求标识被清除,防止后续请求拿到旧数据。

备选:从ReactiveSecurityContext获取Principal

如果exchange.getPrincipal()无法满足需求,也可以通过安全上下文直接获取认证信息:

return ReactiveSecurityContextHolder.getContext()
        .map(SecurityContext::getAuthentication)
        .filter(Objects::nonNull)
        .map(Authentication::getName)
        .defaultIfEmpty("anonymous")
        .doOnNext(userName -> MDC.put(USER_NAME, userName))
        .then(chain.filter(exchange))
        .doFinally(signalType -> {
            MDC.remove(REQUEST_ID);
            MDC.remove(USER_NAME);
        });

进阶:解决线程切换导致MDC值丢失

WebFlux的操作可能在不同线程间切换,ThreadLocal实现的MDC无法跨线程传递,此时推荐将请求标识存入Reactive Context,再通过日志框架扩展提取:

  1. 将值写入Reactive Context:
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    String requestId = UUID.randomUUID().toString();
    return exchange.getPrincipal()
            .map(Principal::getName)
            .defaultIfEmpty("anonymous")
            .flatMap(userName -> chain.filter(exchange)
                    .contextWrite(ctx -> ctx
                            .put("request-id", requestId)
                            .put("user-name", userName)));
}
  1. 自定义Logback转换器从Context提取值(示例):
public class ReactiveContextLogConverter extends ClassicConverter {
    @Override
    public String convert(ILoggingEvent event) {
        ContextView context = ReactiveSecurityContextHolder.getContextView();
        return context.getOrDefault("request-id", "unknown");
    }
}

在logback配置中注册转换器并使用:

<conversionRule conversionWord="requestId" converterClass="com.yourpackage.ReactiveContextLogConverter"/>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - requestId:%requestId, user:%userName - %msg%n</pattern>

内容的提问来源于stack exchange,提问作者Sprivro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 17:55:17