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

部署至EC2后Spring Boot的XSRF-TOKEN Cookie未在浏览器保存

问题

我在AWS EC2上部署了采用会话认证授权的Spring Boot API应用,通过Nginx反向代理路由至Docker中运行的应用,已启用CSRF保护以防护POST路由(含登录路由,尽管其在安全过滤器链中设为允许所有)。另有独立EC2服务器托管Angular UI,当前实现为登录页面加载时,通过CSRF控制器向Spring应用请求获取CSRF Token。本地开发时流程正常,但部署至EC2实例后,登录请求返回401未授权,经查X-XSRF-TOKEN未保存至浏览器。禁用CSRF保护验证后,用户登录会话Cookie可正常保存。请问为何部署后X-XSRF-TOKEN无法保存?注:两个EC2实例均配置SSL证书。

相关配置

Angular拦截器

@Injectable()
export class CsrfInterceptor implements HttpInterceptor {

  tokenExtractor: HttpXsrfTokenExtractor = inject(HttpXsrfTokenExtractor);

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const headerName = 'X-XSRF-TOKEN';
    const token = this.tokenExtractor.getToken() as string;

    if (token !== null && !req.headers.has(headerName)) {
      req = req.clone({ headers: req.headers.set(headerName, token) });
    }

    return next.handle(req);
  }

}

Spring Boot跨域配置

CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(allowOrigins);
        configuration.setAllowedMethods(List.of("GET", "PUT", "POST", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(List.of(CONTENT_TYPE, ACCEPT, "X-XSRF-TOKEN"));
        configuration.setExposedHeaders(List.of(LOCATION, "X-XSRF-TOKEN"));
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);

Cookie过滤器

@Slf4j
    private static final class CsrfCookieFilter extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(
                HttpServletRequest request,
                HttpServletResponse response,
                FilterChain filterChain
        ) throws ServletException, IOException {
            CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
            response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken());
            filterChain.doFilter(request, response);
        }
    }

CSRF控制器

@GetMapping(path = "/csrf")
    public CsrfToken csrf(CsrfToken csrfToken) {
        return csrfToken;
    }

安全过滤器链中的CSRF配置

.csrf(csrf -> csrf
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
                )

Spring应用所在EC2实例的Nginx配置

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;

        server_name server.example.com;

 location / {
                proxy_pass http://localhost:8080;
                proxy_redirect off;
                proxy_set_header   Host             $host;
                proxy_set_header   X-Real-IP        $remote_addr;
        }
 }
@Bean
    public CookieSerializer cookieSerializer() {
        var domain = this.environment.getProperty("${server.servlet.session.cookie.domain}");
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setCookieName("JSESSIONID");
        cookieSerializer.setUseHttpOnlyCookie(true);
        cookieSerializer.setUseSecureCookie(true);
        cookieSerializer.setCookiePath("/");
        cookieSerializer.setSameSite("lax");
        cookieSerializer.setCookieMaxAge(3600);
        cookieSerializer.setDomainName(domain);

        var property = Optional.ofNullable(this.environment.getProperty("spring.profiles.active"));
        if (property.isPresent() && (property.get().equals("dev") || property.get().equals("test"))) {
            cookieSerializer.setUseHttpOnlyCookie(false);
            cookieSerializer.setUseSecureCookie(false);
            cookieSerializer.setCookieMaxAge(1800);
        }
        return cookieSerializer;
    }

本地开发时X-XSRF-TOKEN截图

本地开发时X-XSRF-TOKEN截图

排查与解决思路

结合配置和部署环境,X-XSRF-TOKEN无法保存的核心原因集中在跨域Cookie传递规则、Nginx反向代理配置缺失、CSRF Cookie属性配置错误这几个方面,逐一分析如下:

1. Nginx配置缺失关键Cookie传递头

当前Nginx配置未处理Cookie相关反向代理头,导致Spring设置的CSRF Cookie无法正确透传给前端。需补充以下配置:

location / {
    proxy_pass http://localhost:8080;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    # 新增:传递Cookie相关头
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    # 允许传递Cookie
    proxy_cookie_path / "/; secure; SameSite=Lax";
}
  • X-Forwarded-Proto:告知Spring应用当前请求为HTTPS协议,确保Secure属性的Cookie能被正确设置
  • proxy_cookie_path:修正Cookie的路径和属性,适配HTTPS环境下的跨域要求

2. CSRF Cookie的Domain配置错误

Cookie Bean中domain取值的占位符写法错误,应直接读取配置项:

// 原错误代码:
var domain = this.environment.getProperty("${server.servlet.session.cookie.domain}");
// 修正为:
var domain = this.environment.getProperty("server.servlet.session.cookie.domain");

若部署时未正确配置server.servlet.session.cookie.domain,CSRF Cookie的Domain会与前端域名不匹配,导致浏览器拒绝保存。需确保该配置值为Spring应用的域名(如server.example.com),且与Angular UI的域名处于同一主域。

3. CSRF Cookie的SameSite属性配置

当前Cookie Bean设置SameSite="lax",跨域请求下Lax可能不足以允许Cookie传递。可临时改为SameSite="None"(必须配合Secure属性,你的环境已满足SSL配置):

cookieSerializer.setSameSite("None");

注意:SameSite=None需要浏览器支持,且必须搭配Secure属性,否则浏览器会拒绝保存Cookie。

4. 跨域配置的AllowedOrigins需精确匹配

确保allowOrigins配置的是Angular UI的完整域名(如https://ui.example.com),不能使用通配符*——当AllowCredentials=true时,通配符会被浏览器视为无效。

5. CSRF Token的传递方式问题

Angular拦截器依赖从Cookie中提取XSRF-TOKEN(Angular默认自动读取名为XSRF-TOKEN的Cookie),而你的Cookie过滤器仅将Token放在响应头中,可能导致Angular无法自动捕获。可调整Spring的CSRF配置,确保Cookie正确生成:

.csrf(csrf -> csrf
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
    .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
)

同时,CsrfCookieFilter可保留,但无需额外设置响应头——CookieCsrfTokenRepository已自动设置XSRF-TOKEN Cookie。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 09:22:23