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
相关产品推荐
相关产品推荐

