Spring Java MVC中从SecurityContextHolder获取用户邮箱及Log4j集成问题
解决Spring MVC中Log4j获取登录用户邮箱并输出到日志的问题
首先,你遇到的核心问题是Spring Security默认的UserDetails实现(org.springframework.security.core.userdetails.User)并不包含邮箱字段,所以直接从SecurityContextHolder拿到的用户对象自然没有获取邮箱的方法。咱们一步步来解决:
1. 自定义包含邮箱的UserDetails实现
先创建一个自己的用户类,继承默认的User类(或者直接实现UserDetails接口),把邮箱字段加进去:
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import java.util.Collection; // 继承默认User类,添加邮箱字段 public class CustomUserDetails extends User { private final String email; public CustomUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities, String email) { super(username, password, authorities); this.email = email; } // 提供邮箱的getter方法 public String getEmail() { return email; } }
如果不想继承,也可以直接实现UserDetails接口自己实现所有方法,但继承默认User会更省事。
2. 在UserDetailsService中加载用户数据时填充邮箱
接下来,你的UserDetailsService实现类里,从数据库查询用户信息时要把邮箱一起查出来,然后创建CustomUserDetails对象返回:
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service public class CustomUserDetailsService implements UserDetailsService { // 注入你的用户DAO/Repository private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库查询用户,包含邮箱 UserEntity user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); // 创建自定义的UserDetails对象,传入邮箱 return new CustomUserDetails( user.getUsername(), user.getPassword(), user.getAuthorities(), // 这里要转换为GrantedAuthority集合 user.getEmail() ); } }
3. 将邮箱存入Log4j的MDC(映射诊断上下文)
Log4j的MDC可以让你在整个请求生命周期中存储全局变量,方便在日志格式里引用。我们可以用Spring Security过滤器来在请求开始时把邮箱放入MDC:
import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.logging.log4j.ThreadContext; // Log4j2用ThreadContext,Log4j1用org.apache.log4j.MDC import java.io.IOException; public class UserEmailMdcFilter extends OncePerRequestFilter { private static final String MDC_EMAIL_KEY = "userEmail"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated() && authentication.getPrincipal() instanceof CustomUserDetails) { // 强制转换为自定义的UserDetails CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); // 把邮箱放入MDC ThreadContext.put(MDC_EMAIL_KEY, userDetails.getEmail()); } else { // 未登录时设置默认值 ThreadContext.put(MDC_EMAIL_KEY, "anonymous"); } filterChain.doFilter(request, response); } finally { // 请求结束后清理MDC,避免内存泄漏 ThreadContext.remove(MDC_EMAIL_KEY); } } }
然后把这个过滤器添加到Spring Security的过滤器链中:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // ... 你的其他Spring Security配置 .addFilterBefore(new UserEmailMdcFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }
4. 在log4j.properties中配置日志格式
现在MDC里已经有userEmail这个键了,直接在日志格式里引用即可:
# Log4j 1.x 配置示例 log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p [%X{userEmail}] %c{1}:%L - %m%n # Log4j 2.x 配置示例(如果用的是Log4j2) appender.stdout.layout.pattern=%d{yyyy-MM-dd HH:mm:ss} %-5p [%X{userEmail}] %c{1}:%L - %m%n
这里的%X{userEmail}就是引用MDC中存储的邮箱值。
额外注意事项
- 如果你的项目用的是Log4j 1.x,MDC类是
org.apache.log4j.MDC,而Log4j2用的是org.apache.logging.log4j.ThreadContext,别搞混了。 - 强制转换
CustomUserDetails时加了类型判断,避免出现ClassCastException,这是个好习惯。 - 未登录的请求,MDC里的邮箱会显示为"anonymous",你可以根据需求调整这个默认值。
内容的提问来源于stack exchange,提问作者KFC
相关产品推荐
相关产品推荐

