Spring Boot MVC集成JWT令牌实现视图鉴权问题求助
集成JWT到Spring MVC视图型Web应用的解决方案
针对你现有基于表单登录+角色权限的Spring Security配置,要集成JWT并适配视图访问,核心是调整登录流程生成JWT、让浏览器自动携带JWT令牌、同时保留原有的角色权限控制逻辑,以下是具体实现步骤:
1. 自定义登录成功处理器,生成并存储JWT
创建登录成功后的处理器,在用户验证通过后生成JWT并存储到HttpOnly Cookie中(浏览器会自动在后续请求中携带):
@Component public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Autowired private JwtUtil jwtUtil; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); String jwtToken = jwtUtil.generateToken(userDetails.getUsername()); // 将JWT存入HttpOnly Cookie,防止XSS攻击 Cookie jwtCookie = new Cookie("JWT_TOKEN", jwtToken); jwtCookie.setHttpOnly(true); jwtCookie.setSecure(false); // 生产环境需改为true(仅HTTPS传输) jwtCookie.setPath("/"); response.addCookie(jwtCookie); // 登录成功后重定向到首页 response.sendRedirect("/"); } }
2. 修改JwtRequestFilter,支持从Cookie读取JWT
调整原有的JWT过滤器,优先从Cookie读取令牌(兼容浏览器视图请求),同时保留Authorization头读取逻辑(兼容API请求):
@Component public class JwtRequestFilter extends OncePerRequestFilter { @Autowired private CustomUserDetailsService customUserDetailsService; @Autowired private JwtUtil jwtUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String jwtToken = null; String username = null; // 从Cookie提取JWT Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if ("JWT_TOKEN".equals(cookie.getName())) { jwtToken = cookie.getValue(); break; } } } // Cookie无令牌时,从Authorization头读取Bearer令牌 if (jwtToken == null) { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { jwtToken = authHeader.substring(7); } } // 验证令牌并设置安全上下文 if (jwtToken != null && SecurityContextHolder.getContext().getAuthentication() == null) { username = jwtUtil.extractUsername(jwtToken); if (username != null) { UserDetails userDetails = customUserDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(jwtToken, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } } filterChain.doFilter(request, response); } }
3. 更新SecurityConfig,启用JWT支持
打开之前注释的无状态会话配置,添加JWT过滤器,并替换登录/登出的处理器:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService customUserDetailsService; @Autowired private JwtRequestFilter jwtRequestFilter; @Autowired private JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler; @Autowired private JwtLogoutSuccessHandler jwtLogoutSuccessHandler; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/","/registration/**","/logout","/login").permitAll() .antMatchers("/students/**").hasRole("Admin") .antMatchers("/subjects/**").hasAnyRole("User","Admin") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .successHandler(jwtAuthenticationSuccessHandler) // 替换默认登录成功逻辑 .and() .logout() .logoutSuccessHandler(jwtLogoutSuccessHandler) // 自定义登出逻辑 .logoutSuccessUrl("/login?logout") .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 启用无状态会话 .and() .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); // 添加JWT过滤器 } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
4. 自定义登出处理器,清除JWT Cookie
创建登出成功处理器,清除存储JWT的Cookie:
@Component public class JwtLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 清除JWT Cookie Cookie jwtCookie = new Cookie("JWT_TOKEN", null); jwtCookie.setHttpOnly(true); jwtCookie.setSecure(false); jwtCookie.setPath("/"); jwtCookie.setMaxAge(0); // 立即过期 response.addCookie(jwtCookie); response.sendRedirect("/login?logout"); } }
5. 视图页面适配(Thymeleaf示例)
如果使用Thymeleaf,可以通过Spring Security表达式控制视图元素的显示:
<!DOCTYPE html> <html xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <div sec:authorize="!isAuthenticated()"> <a href="/login">登录</a> <a href="/registration">注册</a> </div> <div sec:authorize="isAuthenticated()"> <p>欢迎, <span sec:authentication="name"></span></p> <div sec:authorize="hasRole('Admin')"> <a href="/students">学生列表</a> </div> <div sec:authorize="hasAnyRole('User','Admin')"> <a href="/subjects">科目列表</a> </div> <a href="/logout">登出</a> </div> </body> </html>
需确保引入Thymeleaf Spring Security依赖:
<dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity6</artifactId> </dependency>
测试流程
- 访问
/login页面,输入Admin或User凭证登录,登录成功后浏览器会自动存储JWT Cookie并跳转首页 - 访问
/students(仅Admin可见)或/subjects(User/Admin可见),JWT过滤器会自动验证令牌并授权访问 - 点击登出,Cookie被清除,跳转回登录页面
内容的提问来源于stack exchange,提问作者Anxheloo
相关产品推荐
相关产品推荐

