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

Spring Boot中如何自动为所有请求携带JWT Token

解决方案:自动携带JWT Token访问受保护端点

问题分析

直接在浏览器地址栏输入受保护端点地址时,浏览器发起的请求不会自动携带localStorage中的Token——因为localStorage数据不会随HTTP请求自动发送。之前尝试Cookie方案未成功,核心原因是现有Filter未从Cookie中读取Token,或Cookie配置不符合要求。


方案1:完善后端Filter,支持从Cookie读取Token

修改现有JwtAuthenticationFilter的resolveToken方法,让它优先从请求头读取Token,读取不到时再从Cookie提取:

@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

    public JwtAuthenticationFilter(
            JwtService jwtService,
            UserDetailsService userDetailsService
    ) {
        this.jwtService = jwtService;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String token = resolveToken(request);
        log.debug("Token of " + request.getRequestURI() + " : " + token);
        if (token != null && jwtService.isTokenValid(token)) {
            String username = jwtService.extractUsername(token);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (jwtService.isTokenValid(token, userDetails)) {
                    UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(auth);
                }
            }
            // 可选:Token刷新逻辑,刷新后更新Cookie和响应头
            // String refreshedToken = jwtService.refreshToken(token);
            // setTokenToCookie(response, refreshedToken);
            // response.addHeader("Authorization", "Bearer " + refreshedToken);
        }

        filterChain.doFilter(request, response);
    }

    private String resolveToken(HttpServletRequest request) {
        // 优先从请求头读取Token
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        // 请求头无Token时,从Cookie读取
        return extractTokenFromCookies(request.getCookies());
    }

    private String extractTokenFromCookies(Cookie[] cookies) {
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("jwtToken".equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    // 新增:登录成功后将Token写入Cookie的工具方法
    public void setTokenToCookie(HttpServletResponse response, String token) {
        Cookie jwtCookie = new Cookie("jwtToken", token);
        jwtCookie.setHttpOnly(false); // 需前端读取则设为false,仅后端使用建议设为true提升安全性
        jwtCookie.setSecure(false); // 生产环境建议设为true(仅HTTPS传输)
        jwtCookie.setPath("/"); // 所有路径请求都携带该Cookie
        jwtCookie.setMaxAge(24 * 60 * 60); // 有效期1天
        response.addCookie(jwtCookie);
    }
}

登录接口调整

在登录成功生成Token后,调用上述setTokenToCookie方法将Token写入Cookie:

@RestController
@RequestMapping("/auth")
public class AuthController {

    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;
    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public AuthController(JwtService jwtService, UserDetailsService userDetailsService, 
                          PasswordEncoder passwordEncoder, JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtService = jwtService;
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request, HttpServletResponse response) {
        // 用户名密码验证逻辑
        UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
        if (!passwordEncoder.matches(request.getPassword(), userDetails.getPassword())) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials");
        }
        // 生成JWT Token
        String token = jwtService.generateToken(userDetails);
        // 将Token写入Cookie
        jwtAuthenticationFilter.setTokenToCookie(response, token);
        // 返回Token给前端存入localStorage
        return ResponseEntity.ok(new LoginResponse(token));
    }
}

方案2:前端全局处理页面加载时的Token携带(补充方案)

对于直接输入地址的场景,页面加载前无法自动携带localStorage的Token,可在全局脚本中添加逻辑:检查localStorage是否有Token,若当前页面是受保护页面且未认证,则用fetch重新请求并渲染,或重定向到登录页。

在公共页面模板或全局JS文件中添加:

// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
    const protectedPaths = ['/profile', '/dashboard']; // 配置受保护路径列表
    const currentPath = window.location.pathname;
    
    // 判断当前路径是否需要认证
    if (protectedPaths.includes(currentPath)) {
        const token = localStorage.getItem('jwtToken');
        if (token) {
            // 携带Token重新请求当前页面
            fetch(window.location.href, {
                method: 'GET',
                headers: {
                    'Authorization': 'Bearer ' + token
                }
            })
            .then(response => {
                if (response.ok) {
                    return response.text();
                } else {
                    throw new Error('Unauthorized');
                }
            })
            .then(html => {
                document.open();
                document.write(html);
                document.close();
            })
            .catch(error => {
                // 认证失败,重定向到登录页
                window.location.href = '/login';
            });
        } else {
            // 无Token,直接跳转到登录页
            window.location.href = '/login';
        }
    }
});

// 登录成功后将Token存入localStorage
function handleLoginSuccess(token) {
    localStorage.setItem('jwtToken', token);
    // 可选:若后端未设置Cookie,前端手动写入
    document.cookie = `jwtToken=${token}; path=/; max-age=86400`;
    window.location.href = '/profile';
}

关键注意事项

  • Cookie安全性:生产环境建议将Cookie的HttpOnly设为true(防止XSS攻击),Secure设为true(仅HTTPS传输),此时前端无法读取Cookie,但浏览器会自动携带,后端Filter仍能正常读取。
  • Token刷新:若Token有过期时间,需在Filter中添加Token刷新逻辑,刷新后更新Cookie和localStorage中的Token。
  • 路径匹配:Cookie的Path需设为/,确保所有路径的请求都携带该Cookie。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 20:33:13