Spring Security问题:控制器认证详情为空及Spring Session整合疑问
嘿,我来帮你搞定这两个Spring Security的问题,咱们一个一个来梳理:
这情况我遇到过好几次,主要是这两个原因导致的,对应解决方案给你列出来:
原因一:认证流程没设置details
Web环境下,Authentication的details默认是由WebAuthenticationDetailsSource生成的,里面包含请求IP、SessionID这些信息。如果你的自定义登录过滤器(比如重写了UsernamePasswordAuthenticationFilter)没调用setDetails()方法,details就会是null。
解决办法:在你的认证过滤器里补上这段代码:@Override protected Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( obtainUsername(request), obtainPassword(request)); // 关键:设置details setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }要是你用的是Spring Security默认的表单登录,检查下有没有误配置自定义的
AuthenticationDetailsSource,如果有得确保它正确实现了生成details的逻辑。原因二:SecurityContext获取方式不对
如果你不是通过SecurityContextHolder.getContext().getAuthentication()拿Authentication,而是手动new了一个对象,那details肯定是null。另外,要是在异步线程里获取,默认ThreadLocal的SecurityContext不会传递过去,得改策略:// 在启动类或者配置类里设置 SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);控制器里正确的获取方式应该是这样:
@GetMapping("/profile") public ResponseEntity<?> getProfile() { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); WebAuthenticationDetails details = (WebAuthenticationDetails) auth.getDetails(); // 现在就能拿到IP、SessionID这些信息了 return ResponseEntity.ok(details.getRemoteAddress()); }
看你贴的代码没写完,但结合场景,我猜大概率是监听器和Redis Session的同步、上下文获取的问题,给你几个常见的解决方案:
首先补全正确的监听器代码
你可能是想在登录成功后操作Redis Session,比如记录登录日志或者更新Session属性,完整的监听器应该这样写:
import org.springframework.context.ApplicationListener; import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent; import org.springframework.security.core.Authentication; import org.springframework.session.Session; import org.springframework.session.data.redis.RedisIndexedSessionRepository; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; @Component public class LoginSuccessListener implements ApplicationListener<InteractiveAuthenticationSuccessEvent> { private final RedisIndexedSessionRepository sessionRepository; // 注入Redis Session仓库,用来操作Redis里的Session public LoginSuccessListener(RedisIndexedSessionRepository sessionRepository) { this.sessionRepository = sessionRepository; } @Override public void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) { Authentication auth = event.getAuthentication(); // 优先用事件里的Authentication,比从SecurityContext拿更可靠 // 获取当前请求的Session,同步到Redis ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { HttpServletRequest request = attributes.getRequest(); String sessionId = request.getSession().getId(); Session session = sessionRepository.findById(sessionId); if (session != null) { // 比如记录最后登录时间 session.setAttribute("lastLoginTime", System.currentTimeMillis()); session.setAttribute("loginUser", auth.getName()); sessionRepository.save(session); } } } }
必须配置Session事件发布
用Spring Session Redis时,默认不会发布Session相关事件,得手动加这个Bean,不然监听器可能收不到事件:
@Configuration @EnableRedisHttpSession public class RedisSessionConfig { // 你的Redis连接配置... // 关键:发布Session事件,让监听器能接收到 @Bean public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); } }
调整监听器执行顺序
如果你的监听器执行时,Redis还没把Session保存好,操作就会无效。可以用@Order让它晚一点执行:
import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @Component @Order(Ordered.LOWEST_PRECEDENCE) // 最后执行,确保Session已经同步到Redis public class LoginSuccessListener implements ApplicationListener<InteractiveAuthenticationSuccessEvent> { // ...代码... }
内容的提问来源于stack exchange,提问作者Kirill Linnik

