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

Spring Boot 2.0/Tomcat 8.5日志中Session用户名属性无法显示问题排查

问题分析与解决方案

我来帮你拆解下为什么Tomcat访问日志里的%{username}s总是显示-,以及怎么解决这个问题:

核心原因:容器层与Spring Session的不兼容

你用Redis做Session持久化,大概率是用了Spring Session这类框架对吧?Tomcat的%{xxx}s语法是直接从Tomcat原生内存Session里读取属性,但Spring Session是通过代理HttpSession的方式把数据存在Redis里,Tomcat的Access Log Valve属于容器级别组件,它没法直接读取到Spring Session代理对象里的属性——这就是为什么日志里拿不到你存在Session里的username。

另外还有个细节:你在onAuthenticationSuccess里设置Session属性,这个回调是登录请求处理的最后一步,登录请求本身的日志记录时,username其实还没被写入Session(或者说Tomcat还没拿到这个属性),后续请求也因为Spring Session的代理问题拿不到。

几种可行的解决方案

方案1:改用Spring自定义请求日志(最推荐)

既然用了Spring生态,不如直接在Spring层面做请求日志,这样能轻松拿到Spring Session里的属性,还更灵活:

  1. 写一个请求过滤器,在请求结束时记录日志:
@Component
public class CustomAccessLogFilter extends OncePerRequestFilter {
    private static final Logger logger = LoggerFactory.getLogger(CustomAccessLogFilter.class);
    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z");

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        long start = System.currentTimeMillis();
        filterChain.doFilter(request, response);
        long duration = System.currentTimeMillis() - start;

        // 直接从Spring Session里拿username,拿不到就显示-
        String username = Optional.ofNullable((String) request.getSession().getAttribute("username"))
                .orElse("-");

        // 构造和Tomcat日志格式一致的内容
        String logLine = String.format(
                "%s %s %s [%s] \"%s\" %d %d %dms \"%s\"",
                request.getRemoteAddr(),
                "-", // %l 通常都是-,因为很少用identd服务
                username,
                DATE_FORMAT.format(new Date()),
                request.getMethod() + " " + request.getRequestURI() + " " + request.getProtocol(),
                response.getStatus(),
                response.getContentLength() == -1 ? 0 : response.getContentLength(),
                duration,
                request.getHeader("User-Agent")
        );
        logger.info(logLine);
    }
}
  1. 关闭Tomcat自带的Access Log:
server.tomcat.accesslog.enabled=false

方案2:用%u替代%{username}s(最简单的容器级方案)

Tomcat的%u代表远程认证用户,Spring Security在认证成功后会自动把当前用户名设置到request.getRemoteUser()里,所以你可以直接用这个占位符替代Session属性:

修改你的Tomcat日志配置:

server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %T %{User-Agent}i

如果你的场景里request.getRemoteUser()没自动返回用户名,也可以在onAuthenticationSuccess里手动设置:

@Override 
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { 
    String username = merchandiserRepository.findByUsername(authentication.getName()).getUsername(); 
    request.getSession(true).setAttribute("username", username);
    // 手动设置RemoteUser,确保Tomcat能拿到
    request.setRemoteUser(username);
    logger.warn("Successfully logged in: " + username); 
    response.sendRedirect("/page/1"); 
}

方案3:调整Spring Session同步时机(不推荐,复杂且易出问题)

如果你非要坚持用%{username}s,可以尝试配置Spring Session在修改Session属性时立即同步到Redis,同时确保Tomcat的Access Log Valve在请求完全处理完成后再记录日志。但这种方式需要调整Spring Session的flush-mode为IMMEDIATE,还要修改Tomcat Valve的执行顺序,步骤繁琐且容易引发性能问题,不推荐。

总结

优先选方案1,完全适配Spring生态,后续扩展日志格式也方便;如果不想改日志体系,方案2的%u替换是最省心的办法,能直接复用Tomcat的Access Log。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:00:06