Spring Security无状态模式下,能否在Thymeleaf中使用Authentication对象?
完全可以做到,不过得先搞明白为啥默认情况下拿不到——这和SessionCreationPolicy.STATELESS的特性直接挂钩:
当你把会话策略设为STATELESS时,Spring Security不会把Authentication对象存到HttpSession里,而且每个请求都是独立的,不会保留任何会话信息。Thymeleaf的Spring Security方言里的#authentication本质上是从SecurityContextHolder.getContext().getAuthentication()取对象,但问题出在:
- 登录请求成功后,Authentication只存在于当前请求的SecurityContextHolder里,跳转后的新请求(比如你跳去
/main)如果没带JWT令牌,服务器就不会把Authentication放到SecurityContextHolder中,Thymeleaf自然拿不到。 - 要是改回非无状态策略,Spring Security会把Authentication存入HttpSession,后续请求从Session里读,所以
#authentication就能正常显示了。
下面给你两种可行的解决方案:
方案1:登录成功时手动传递Authentication到跳转页面
如果你只是在登录后的跳转页面需要显示Authentication,可以在登录成功处理器里把Authentication放到Flash属性中,这样跳转后页面能一次性获取到:
首先修改你的successHandler()方法:
private AuthenticationSuccessHandler successHandler() { return (request, response, authentication) -> { // 获取RedirectAttributes来存储Flash属性 RedirectAttributes redirectAttributes = (RedirectAttributes) request.getAttribute( "org.springframework.web.servlet.mvc.support.RedirectAttributesModel"); if (redirectAttributes != null) { // 把Authentication对象存入Flash属性,跳转后页面可读取 redirectAttributes.addFlashAttribute("currentAuth", authentication); } // 跳转到目标页面 response.sendRedirect("/main"); }; }
然后在Thymeleaf页面里直接使用这个Flash属性:
<h1>[[${currentAuth}]]</h1>
Flash属性的特点是只会在跳转后的第一次请求中有效,适合登录后跳转的场景。
方案2:配置JWT过滤器,让所有请求自动填充SecurityContext
这是更符合无状态JWT认证的标准做法:通过自定义过滤器,每次请求时从请求头(或参数)中提取JWT令牌,验证后将Authentication存入SecurityContextHolder,这样Thymeleaf的#authentication就能正常工作了。
步骤1:创建JWT认证过滤器
public class JwtAuthenticationFilter extends OncePerRequestFilter { // 注入自定义的JWT工具类,用于解析和验证令牌 private final JwtTokenUtil jwtTokenUtil; public JwtAuthenticationFilter(JwtTokenUtil jwtTokenUtil) { this.jwtTokenUtil = jwtTokenUtil; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 从请求头中提取JWT令牌(格式通常为Authorization: Bearer xxx) String token = extractTokenFromHeader(request); if (token != null && jwtTokenUtil.isTokenValid(token)) { // 从令牌中解析用户信息,构建Authentication对象 Authentication authentication = jwtTokenUtil.getAuthenticationFromToken(token); // 将Authentication存入SecurityContextHolder SecurityContextHolder.getContext().setAuthentication(authentication); } // 继续执行后续过滤器链 filterChain.doFilter(request, response); } private String extractTokenFromHeader(HttpServletRequest request) { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { return authHeader.substring(7); } return null; } }
步骤2:在Security配置中添加这个过滤器
修改你的SecurityConfiguration:
@Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired MyUserDetailsService myUserDetailsService; @Autowired JwtTokenUtil jwtTokenUtil; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/index", "/main", "/login").permitAll() .anyRequest().authenticated(); http.formLogin() .loginPage("/login") .permitAll() .successHandler(successHandler()); // 添加JWT过滤器,放在UsernamePasswordAuthenticationFilter之前 http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenUtil), UsernamePasswordAuthenticationFilter.class); http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } // 你的successHandler实现,需要在登录成功时生成并返回JWT令牌 private AuthenticationSuccessHandler successHandler() { return (request, response, authentication) -> { String token = jwtTokenUtil.generateToken(authentication); // 将令牌放入响应头,让前端后续请求携带 response.setHeader("Authorization", "Bearer " + token); response.sendRedirect("/main"); }; } }
这样,后续所有请求只要携带有效的JWT令牌,过滤器就会自动把Authentication存入SecurityContextHolder,Thymeleaf里的[[${#authentication}]]就能正常显示了。
总结一下:如果只是登录跳转页面临时显示,用方案1简单快捷;如果是所有需要权限的页面都要显示Authentication,方案2是更规范的无状态认证做法。
内容的提问来源于stack exchange,提问作者Yong D

