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

如何在Java Spring Boot中用Cookie存储Access Token?实现与安全疑问

问题解答

1. 为什么Cookie没出现在Local Storage?

首先明确:你代码里设置的是HttpOnly Cookie,这类Cookie由浏览器直接管理,不会暴露给JavaScript(包括无法写入Local Storage),也不会在document.cookie中可见。这不是bug,而是你开启cookie.setHttpOnly(true)的预期效果——核心目的就是防止XSS脚本窃取Token。

如果硬要把Token存入Local Storage,需要改逻辑:

  • 后端登录接口不在响应里设置Cookie,而是直接把Token返回在响应体中
  • 前端拿到响应里的Token后,手动执行localStorage.setItem('access_token', token)存储

但这种方式会让Token完全暴露给XSS攻击,安全性远不如HttpOnly Cookie。

2. 当前Spring Boot的Cookie实现是否正确?

基础逻辑没问题,但有几个关键优化点:

  • Secure属性:如果网站用HTTPS,必须加cookie.setSecure(true),确保Cookie只在HTTPS请求中传输,避免明文泄露
  • SameSite属性:添加cookie.setSameSite("Strict")或"Lax",抵御CSRF攻击。Strict严格限制Cookie仅同站点请求携带,Lax允许部分跨站GET请求携带
  • 域名配置:多子域名场景下,设置cookie.setDomain(".yourdomain.com"),保证子域名能共享Cookie

优化后的Cookie代码:

Cookie cookie = new Cookie("access_token", token);
cookie.setMaxAge(24 * 60 * 60);
cookie.setHttpOnly(true);
cookie.setSecure(true); // 仅HTTPS环境启用
cookie.setSameSite("Strict");
cookie.setPath("/");
response.addCookie(cookie);

另外前端fetch请求要确保携带Cookie,跨域场景下默认不会发送Cookie,需在配置里加credentials: 'include':

const res = await fetch(
  process.env.NEXT_PUBLIC_BASE_URL + "/api/auth/sign-in",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    credentials: 'include', // 关键:让请求携带Cookie
    body: JSON.stringify({ email, password }),
  }
);

3. Access Token存Cookie是否安全?会不会被XSS攻击?

核心结论:配置了HttpOnly + Secure + SameSite的Cookie,是存储Access Token的安全方案,安全性远高于Local Storage。

  • XSS防护:HttpOnly Cookie无法被JavaScript读取,哪怕页面存在XSS漏洞,攻击者也拿不到Token
  • CSRF风险:这是Cookie方案的主要隐患,但可以通过SameSite属性、后端校验CSRF Token解决——比如在表单或请求头里携带CSRF Token,后端验证Token与Cookie的关联

如果用Local Storage存Token:

  • 完全无法抵御XSS攻击,只要页面被注入恶意脚本,就能直接读取Token并发送到攻击者服务器
  • 没有CSRF风险,但XSS风险的危害远大于Cookie的CSRF风险

私有路由授权的中间件实现建议

后端(Spring Boot):

写一个拦截器,拦截私有路由并校验Cookie中的Token:

@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Cookie[] cookies = request.getCookies();
        String token = null;
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("access_token".equals(cookie.getName())) {
                    token = cookie.getValue();
                    break;
                }
            }
        }

        if (token == null || !jwtTokenProvider.validateToken(token)) {
            response.sendError(HttpStatus.UNAUTHORIZED.value(), "Unauthorized");
            return false;
        }

        // 解析Token,把用户信息存入请求上下文
        String username = jwtTokenProvider.getUsernameFromToken(token);
        request.setAttribute("username", username);
        return true;
    }
}

之后注册拦截器,指定需要拦截的私有路由即可。

前端(Next.js):

用middleware.ts拦截私有路由,检查Cookie是否存在(或调用后端接口验证Token有效性):

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // 检查请求中的access_token Cookie
  const token = request.cookies.get('access_token');
  
  if (!token) {
    // 未登录则重定向到登录页
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // 可选:调用后端API验证Token是否有效
  // const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/auth/validate`, {
  //   credentials: 'include'
  // });
  // if (!res.ok) {
  //   return NextResponse.redirect(new URL('/login', request.url));
  // }

  return NextResponse.next();
}

// 指定需要拦截的私有路由
export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*'],
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 06:42:46