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

Spring Boot部署在Heroku时,如何为JSESSIONID Cookie设置SameSite=None以解决Chrome跨域Cookie拦截问题

解决Spring Boot中JSESSIONID Cookie的SameSite=None设置问题

我来帮你搞定Chrome里JSESSIONID被拦截的问题!这个情况是因为Chrome现在对跨站Cookie的SameSite属性有严格要求,必须同时设置SameSite=None和Secure才能在跨站场景(比如你的Angular应用调用Heroku上的API)下正常工作,而Spring Boot默认的JSESSIONID并没有配置这些属性。

下面给你几种可行的解决方案,按推荐程度排序:

方法一:通过Spring Boot配置文件直接设置(最简单)

如果你的Spring Boot版本是2.6及以上,可以直接在application.properties或者application.yml里添加配置,全局修改JSESSIONID的Cookie属性:

application.properties:

# 设置JSESSIONID的SameSite属性为None
server.servlet.session.cookie.same-site=None
# 必须开启Secure,因为SameSite=None要求Cookie只能通过HTTPS传输(Heroku是HTTPS环境,没问题)
server.servlet.session.cookie.secure=true
# 可选:如果需要Cookie在整个站点可用,设置路径为/
server.servlet.session.cookie.path=/

application.yml:

server:
  servlet:
    session:
      cookie:
        same-site: None
        secure: true
        path: /

这种方式不需要修改代码,直接配置就行,非常方便。

如果你的项目用了Spring Security,也可以在SecurityConfig的configure(HttpSecurity http)方法里直接配置session的Cookie属性,这样更贴合安全配置逻辑:

修改你的SecurityConfig类:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .authorizeRequests()
            .antMatchers(HttpMethod.POST, PUBLIC_MATCHERS_POST).permitAll()
            .antMatchers(HttpMethod.GET, PUBLIC_MATCHERS_GET).permitAll()
            .antMatchers(PUBLIC_MATCHERS).permitAll()
            .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
            .anyRequest().authenticated()
            .and().formLogin()
            .and().httpBasic()
            .and().logout().logoutUrl("/logout").logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)))
            .clearAuthentication(true).invalidateHttpSession(true)
            .deleteCookies("JSESSIONID", "XSRF-TOKEN")
            // 新增JSESSIONID Cookie配置
            .and().sessionManagement()
            .sessionCookieConfig()
            .sameSite(SameSiteCookie.NONE) // 设置SameSite为None
            .secure(true) // 开启Secure属性
            .path("/"); // 设置Cookie路径为整个站点
}

注意:这个方式需要Spring Security版本在5.6及以上,因为SameSiteCookie枚举是在这个版本引入的。

方法三:自定义Filter修改JSESSIONID Cookie属性(兜底方案)

如果上面两种方法因为版本限制无法使用,可以自定义一个Filter来修改响应中的JSESSIONID Cookie头。你可以修改现有的CorsFilter,或者新增一个Filter:

修改后的CorsFilter代码:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        // 先设置CORS相关头(保持你原来的配置)
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Content-Type, X-Requested-With, Accept, Origin, Access-Control-Request-Method, " +
                "Access-Control-Request-Headers, Authorization, if-modified-since, remember-me, " +
                "x-csrf-token, x-xsrf-token, xsrf-token ");
        response.addHeader("Access-Control-Expose-Headers", "xsrf-token");
        response.addHeader("Access-Control-Allow-Headers", "x-csrf-token, x-xsrf-token");

        // 用包装类捕获Spring生成的Set-Cookie头
        CustomHttpServletResponseWrapper wrappedResponse = new CustomHttpServletResponseWrapper(response);
        chain.doFilter(request, wrappedResponse);

        // 处理所有Cookie,为JSESSIONID添加SameSite=None和Secure
        for (String cookie : wrappedResponse.getSetCookieHeaders()) {
            String updatedCookie;
            if (cookie.contains("JSESSIONID")) {
                // 确保添加SameSite=None和Secure,避免重复添加
                if (!cookie.contains("SameSite=None")) {
                    updatedCookie = cookie + "; SameSite=None; Secure";
                } else {
                    updatedCookie = cookie;
                }
            } else {
                updatedCookie = cookie;
            }
            response.addHeader("Set-Cookie", updatedCookie);
        }

        // 你的locale Cookie保持不变
        response.addHeader("Set-Cookie", "locale=pt-BR; HttpOnly; Secure; SameSite=None;");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void destroy() {}

    // 自定义HttpServletResponse包装类,用于捕获Set-Cookie头
    private static class CustomHttpServletResponseWrapper extends HttpServletResponseWrapper {
        private final List<String> setCookieHeaders = new ArrayList<>();

        public CustomHttpServletResponseWrapper(HttpServletResponse response) {
            super(response);
        }

        @Override
        public void addHeader(String name, String value) {
            if ("Set-Cookie".equalsIgnoreCase(name)) {
                setCookieHeaders.add(value);
            } else {
                super.addHeader(name, value);
            }
        }

        @Override
        public void setHeader(String name, String value) {
            if ("Set-Cookie".equalsIgnoreCase(name)) {
                setCookieHeaders.clear();
                setCookieHeaders.add(value);
            } else {
                super.setHeader(name, value);
            }
        }

        public List<String> getSetCookieHeaders() {
            return setCookieHeaders;
        }
    }
}

关键注意点

  1. 必须同时设置SameSite=None和Secure:Chrome明确要求,跨站Cookie设置SameSite=None时必须开启Secure,否则会被拦截。Heroku是HTTPS环境,所以这个要求完全满足。
  2. CORS配置要正确:确保你的allowedOrigins是具体的域名(不能用*),并且allowCredentials(true)已经开启,你现有配置里已经做了这点,没问题。
  3. 测试验证:修改后部署到Heroku,打开Chrome开发者工具(F12),进入Application -> Cookies,查看你的API域名下的JSESSIONID,确认SameSite属性为None,Secure为true。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 14:54:06