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

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();
    }
}

创建登出成功处理器,清除存储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>

测试流程

  1. 访问/login页面,输入Admin或User凭证登录,登录成功后浏览器会自动存储JWT Cookie并跳转首页
  2. 访问/students(仅Admin可见)或/subjects(User/Admin可见),JWT过滤器会自动验证令牌并授权访问
  3. 点击登出,Cookie被清除,跳转回登录页面

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 13:35:13