Spring Boot与React跨域部署:SameSite Cookie与CORS配置实战指南

发布时间:2026/7/17 4:55:45
Spring Boot与React跨域部署:SameSite Cookie与CORS配置实战指南 1. 项目概述为什么跨栈部署必须关注Cookie安全最近在帮一个团队排查一个“诡异”的线上问题他们用Spring Boot 3.x开发的后端API搭配React 18构建的前端应用在本地开发环境一切正常但部署到生产环境后用户登录状态总是莫名其妙地丢失。尤其是在Chrome和Edge浏览器上问题复现率极高。前端的网络请求明明显示200但后端Session就是认不出来导致用户频繁被踢回登录页。经过一番抓包和调试问题的根源直指一个看似不起眼实则至关重要的HTTP响应头——Set-Cookie中的SameSite属性。这不是一个新技术问题但随着主流浏览器尤其是Chrome安全策略的持续收紧它在2025年的全栈开发实践中已经从一个“最佳实践”升级为“必须正确配置”的生产级红线。如果你的技术栈恰好是Spring Boot React并且采用了前后端分离部署即前端域名与后端API域名不同那么你几乎百分之百会踩进这个坑。简单来说SameSite是浏览器用来控制Cookie在跨站Cross-Site请求中是否会被发送的安全策略。它有三个值Strict、Lax和None。在默认情况下现代浏览器对未明确声明SameSite的Cookie会将其视为Lax。这意味着从前端站点例如https://app.yourcompany.com发往后端API例如https://api.yourcompany.com的异步请求如fetch或axios发起的请求浏览器将不会自动携带Session Cookie如JSESSIONID。这就是导致登录状态“消失”的罪魁祸首。本篇文章我将以一个真实的Spring Boot 3.3 React 18项目为例带你完整走一遍从问题定位、原理剖析、后端配置、前端适配到生产验证的全过程。这不是一篇简单的配置教程我会重点拆解每个决策背后的“为什么”并分享我在多个项目中趟过的坑和总结出的实战技巧确保你的跨栈应用在2025年的浏览器环境下坚如磐石。2. 核心原理深度拆解SameSite、CORS与凭证安全在动手改代码之前我们必须把底层原理吃透。否则你只会机械地复制配置一旦遇到细微差异又会陷入无尽的调试。跨栈安全部署的核心是浏览器安全模型它主要围绕两个机制工作同源策略SOP和Cookie安全策略。我们的配置本质上是在安全规则的边界上为合法的跨域请求开一道“门”。2.1 SameSite策略的三副面孔SameSite属性直接决定了Cookie的“活动范围”。SameSiteStrict最为严格。Cookie仅在同站Same-Site请求中被发送。同站指的是“协议域名端口”完全一致。这意味着即使用户从https://app.com点击一个指向https://api.com的链接浏览器也不会携带Strict模式的Cookie。它完全禁止跨站使用适用于极高安全要求的场景如银行交易。SameSiteLax现代浏览器的默认行为。它放宽了限制允许在顶级导航如点击链接或GET请求的跨站场景下发送Cookie但会阻止在跨站的子请求如图片、iframe、AJAX/fetch中发送。这对于防止CSRF攻击是有效的但它正是导致我们SPA单页应用登录失败的元凶。因为React应用通过fetch或axios调用API属于跨站子请求Lax策略下的Session Cookie不会被发送。SameSiteNone允许Cookie在跨站请求中被发送但有一个至关重要的前提必须同时设置Secure属性。Secure意味着Cookie只能通过HTTPS协议传输。这是支持跨域单点登录SSO、嵌入式组件等功能的必要条件。我们的目标就是将后端的JSESSIONID设置为SameSiteNone; Secure。关键心法设置SameSiteNone而不设置Secure在Chrome等浏览器中会被直接忽略或拒绝Cookie根本不会生效。这是铁律。2.2 CORS跨源资源共享与Credentials凭证光有SameSiteNone还不够。前端向后端发起跨域请求时还受到CORS规则的限制。要让浏览器在跨域请求中携带Cookie即凭证必须满足以下三个条件缺一不可前端请求显式声明携带凭证在发起请求时如使用fetch或axios必须设置credentials: includefetch或withCredentials: trueaxios。后端响应显式允许携带凭证在后端的CORS配置中必须设置allowCredentials(true)。后端响应不能使用通配符*当allowCredentials为true时响应头Access-Control-Allow-Origin不能是通配符*必须明确指定一个或多个前端源Origin例如https://app.yourcompany.com。这三者与SameSiteNone; Secure共同构成了跨栈认证的“四重奏”。任何一个环节配置错误都会导致认证链条断裂。2.3 实战场景推演假设我们的部署架构如下前端React应用部署在https://www.myapp.com后端Spring Boot API部署在https://api.myapp.com用户访问前端点击登录。前端向https://api.myapp.com/login发送POST请求。错误配置后端返回的Set-Cookie: JSESSIONIDxxx没有SameSite属性浏览器默认为Lax且前端请求未设置withCredentials。结果浏览器根据Lax策略拒绝在此跨站POST请求中发送或接收该Cookie。登录失败。正确配置后端配置JSESSIONID为SameSiteNone; Secure; HttpOnly。后端CORS配置allowCredentials(true)且allowedOrigins(“https://www.myapp.com”)。前端登录请求设置withCredentials: true。结果浏览器允许接收并存储这个Cookie。在后续前端向同一后端域名发起的请求中只要设置了withCredentials: true浏览器就会自动携带这个Cookie会话保持成功。3. Spring Boot后端配置实战2025版理解了原理我们开始动手。Spring Boot在2.6和3.x版本中对Cookie和CORS的支持有了显著改进提供了多种清晰的方式。我将按推荐顺序从最优雅到最灵活逐一详解。3.1 方案一应用配置文件最推荐如果你的Spring Boot版本在2.6及以上强烈建议使用3.x这是最简洁、最无侵入的方式。Spring Boot自动配置会读取这些属性并应用到内嵌的Servlet容器如Tomcat的Cookie处理器上。application.yml 配置示例server: servlet: session: cookie: # 关键设置为None以允许跨站携带 same-site: none # 关键SameSiteNone时必须为true且要求HTTPS环境 secure: true # 建议设置Cookie路径为根确保应用内所有路径可用 path: / # 可选但推荐防止XSS攻击JavaScript无法通过document.cookie读取 http-only: true # 可选Cookie名称默认就是JSESSIONID name: JSESSIONID # 可选会话过期时间秒 max-age: 1800application.properties 配置示例server.servlet.session.cookie.same-sitenone server.servlet.session.cookie.securetrue server.servlet.session.cookie.path/ server.servlet.session.cookie.http-onlytrue实操心得一关于none的大小写在YAML中none通常被解析为字符串“none”这是正确的。但在某些早期版本或特定序列化场景中可能需要引号。如果你发现配置未生效可以尝试加上引号same-site: “None”。在Properties文件中字符串值无需特别处理。最可靠的方法是部署后直接打开浏览器开发者工具在“应用”-“Cookie”选项卡下查看你的API域名下JSESSIONID的SameSite属性是否显示为None。3.2 方案二通过Spring Security配置集成安全框架时适用如果你的项目使用了Spring Security在Security配置中统一管理Cookie策略在逻辑上更清晰。这尤其适用于同时管理多种Cookie如Session Cookie和Remember-Me Cookie的场景。适用于Spring Security 5.6 的配置import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import static org.springframework.security.config.Customizer.withDefaults; Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 1. 配置CORS (需结合单独的CorsConfigurationSource Bean) .cors(withDefaults()) // 2. 会话管理 - 配置Session Cookie .sessionManagement(session - session .sessionFixation().changeSessionId() .sessionAuthenticationStrategy(sessionAuthenticationStrategy()) ) // 3. 其他安全配置... .authorizeHttpRequests(authz - authz .requestMatchers(/api/public/**).permitAll() .anyRequest().authenticated() ) .formLogin(withDefaults()) .httpBasic(withDefaults()); // 4. 关键自定义Session Cookie配置Spring Security 5.6 // 注意此方法可能因版本略有不同核心是获取并修改SessionCookieConfig http.getConfigurer(org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer.class) .sessionAuthenticationStrategy(sessionAuthenticationStrategy()); return http.build(); } // 创建一个策略来配置Cookie Bean public SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new SessionAuthenticationStrategy() { Override public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { // 此方法在认证成功后调用可以在这里修改Cookie但更推荐下面的Filter方案 } }; } }更通用且可靠的Filter方案对于Cookie配置一个更底层、兼容性更好的方式是实现一个Filter来修改HttpServletResponse的Cookie。Spring Security也基于此。import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.io.IOException; Component Order(Ordered.HIGHEST_PRECEDENCE) // 确保此Filter最早执行 public class CookieAttributeFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse res (HttpServletResponse) response; // 使用包装类拦截即将写入的Cookie CookieHeaderResponseWrapper wrappedResponse new CookieHeaderResponseWrapper(res); chain.doFilter(req, wrappedResponse); } static class CookieHeaderResponseWrapper extends HttpServletResponseWrapper { public CookieHeaderResponseWrapper(HttpServletResponse response) { super(response); } Override public void addHeader(String name, String value) { if (“Set-Cookie”.equalsIgnoreCase(name)) { // 修改JSESSIONID Cookie的属性 if (value.contains(“JSESSIONID”)) { // 确保包含必要的属性 String newValue ensureCookieAttributes(value); super.addHeader(name, newValue); return; } } super.addHeader(name, value); } private String ensureCookieAttributes(String cookieHeader) { // 这是一个简化的逻辑实际应用可能需要更严谨的解析 if (!cookieHeader.contains(“SameSite”)) { cookieHeader “; SameSiteNone”; } if (!cookieHeader.contains(“Secure”)) { cookieHeader “; Secure”; } // 确保HttpOnly如果业务需要 if (!cookieHeader.contains(“HttpOnly”)) { cookieHeader “; HttpOnly”; } return cookieHeader; } } }实操心得二Filter方案的陷阱自定义Filter修改Cookie头听起来很强大但容易出错。主要问题在于Set-Cookie头的解析和重建非常繁琐容易破坏原有的属性如PathMax-Age。除非你对Servlet API非常熟悉或者有特殊的多Cookie处理逻辑否则优先推荐使用方案一配置文件。它由Spring Boot和Tomcat底层处理更加稳健。3.3 方案三全局CORS配置必须与Cookie配置协同无论采用哪种Cookie配置方案CORS配置都是必须的。在Spring Boot中推荐使用WebMvcConfigurer对于Spring MVC或WebFluxConfigurer对于WebFlux进行全局配置。Spring MVC 全局CORS配置import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(“/api/**”) // 配置应用于哪些路径模式 // !! 关键不能是”*”必须是具体的前端源允许多个 .allowedOrigins(“https://www.myapp.com”, “https://staging.myapp.com”) .allowedMethods(“GET”, “POST”, “PUT”, “DELETE”, “OPTIONS”, “PATCH”) .allowedHeaders(“*”) // 根据实际情况调整如”Authorization”, “Content-Type”, “X-Requested-With” .exposedHeaders(“X-CSRF-TOKEN”) // 如果需要暴露自定义头给前端 .allowCredentials(true) // !! 关键必须为true以允许携带Cookie .maxAge(3600); // 预检请求缓存时间秒 } }重要提示在Spring Framework 5.3 / Spring Boot 2.4之后allowedOrigins不支持使用*通配符且allowCredentials为true。如果你需要动态允许多个源可以使用allowedOriginPatterns方法它支持通配符模式但在设置allowCredentials(true)时仍需谨慎。registry.addMapping(“/api/**”) .allowedOriginPatterns(“https://*.myapp.com”, “http://localhost:[*]”) // 使用模式匹配 .allowCredentials(true) …;4. React前端适配与请求配置后端配置妥当后前端也需要进行相应的调整以确保发出的每个请求都符合“携带凭证”的约定。4.1 使用Axios推荐Axios是React生态中最常用的HTTP客户端。你需要创建一个配置了withCredentials的Axios实例并在整个应用中使用它。// src/utils/axiosInstance.js import axios from ‘axios’; // 创建配置了基础URL和凭证的Axios实例 const axiosInstance axios.create({ baseURL: process.env.REACT_APP_API_BASE_URL || ‘https://api.myapp.com’, // 从环境变量读取 withCredentials: true, // !! 关键允许跨域请求携带Cookie timeout: 10000, headers: { ‘Content-Type’: ‘application/json’, }, }); // 可选添加请求拦截器例如添加认证Token axiosInstance.interceptors.request.use( (config) { const token localStorage.getItem(‘authToken’); // 或其他获取token的方式 if (token) { config.headers.Authorization Bearer ${token}; } return config; }, (error) { return Promise.reject(error); } ); // 可选添加响应拦截器例如处理通用错误 axiosInstance.interceptors.response.use( (response) response, (error) { if (error.response?.status 401) { // 处理未授权跳转登录 window.location.href ‘/login’; } return Promise.reject(error); } ); export default axiosInstance;然后在你的组件或服务中导入并使用这个实例// src/services/authService.js import axiosInstance from ‘../utils/axiosInstance’; export const login async (credentials) { const response await axiosInstance.post(‘/auth/login’, credentials); return response.data; }; export const getProfile async () { const response await axiosInstance.get(‘/user/profile’); return response.data; };4.2 使用原生Fetch API如果你更喜欢或需要使用原生的fetchAPI配置如下async function fetchWithAuth(url, options {}) { const defaultOptions { credentials: ‘include’, // !! 关键相当于axios的withCredentials: true headers: { ‘Content-Type’: ‘application/json’, …options.headers, }, }; const response await fetch(${process.env.REACT_APP_API_BASE_URL}${url}, { …defaultOptions, …options, }); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } return response.json(); } // 使用示例 fetchWithAuth(‘/user/profile’) .then(data console.log(data)) .catch(err console.error(err));4.3 开发环境代理配置在本地开发时前端运行在http://localhost:3000后端运行在http://localhost:8080这同样是跨域。为了保持与生产环境一致的请求行为使用相对路径携带Cookie我们通常使用开发服务器的代理功能。Create React App 项目配置在项目根目录创建或修改setupProxy.js文件如果使用http-proxy-middleware。// src/setupProxy.js const { createProxyMiddleware } require(‘http-proxy-middleware’); module.exports function(app) { app.use( ‘/api’, // 匹配所有以/api开头的请求 createProxyMiddleware({ target: ‘http://localhost:8080’, // 后端服务器地址 changeOrigin: true, // 关键确保Cookie在代理过程中被正确传递 cookieDomainRewrite: ‘localhost’, // 将后端Set-Cookie头中的Domain重写为localhost onProxyReq: (proxyReq, req, res) { // 如果需要可以在这里添加或修改请求头 // 例如如果后端需要特定的头来识别代理请求 proxyReq.setHeader(‘X-Forwarded-Host’, req.headers.host); } }) ); };配置后前端代码中请求/api/user/profile会被代理到http://localhost:8080/api/user/profile并且Cookie的传递不受浏览器同源策略限制简化了开发。实操心得三开发环境的Cookie Domain代理配置中的cookieDomainRewrite: ‘localhost’非常关键。后端在开发环境设置的Cookie其Domain可能是localhost:8080。如果不重写这个Cookie对运行在localhost:3000的前端是不可见的。重写后Cookie的Domain变为localhost端口差异被忽略前端就能成功接收和存储Cookie了。生产环境部署在相同父域名下如.myapp.com则无此问题。5. 生产环境部署与验证 checklist配置完成后绝不能只相信代码。必须通过系统的验证来确保一切按预期工作。以下是我在项目上线前必做的检查清单。5.1 后端响应头验证使用curl或 Postman 直接调用你的登录或设置Cookie的API检查响应头。curl -i -X POST “https://api.myapp.com/auth/login” \ -H “Content-Type: application/json” \ -H “Origin: https://www.myapp.com” \ -d ‘{“username”:”test”, “password”:”test”}’在响应中你应该看到类似如下的头信息HTTP/2 200 access-control-allow-credentials: true access-control-allow-origin: https://www.myapp.com set-cookie: JSESSIONIDABC123…; Path/; HttpOnly; Secure; SameSiteNone关键检查点access-control-allow-credentials: trueaccess-control-allow-origin:是一个具体的、与请求头Origin匹配的域名不是*。set-cookie中必须包含Secure和SameSiteNone。注意None的N是大写这是标准写法但大部分浏览器不区分大小写。5.2 浏览器端验证这是最直观的验证方式。打开前端应用在Chrome或Edge中访问https://www.myapp.com。打开开发者工具按F12切换到“应用”(Application) 标签页旧版可能是“存储”Storage。清除现有Cookie在左侧“Cookie”树下选中你的前端和后端域名右键清除所有Cookie确保从干净状态开始测试。执行登录操作在前端页面完成登录。检查Cookie在“应用”-“Cookie”下找到你的后端域名https://api.myapp.com。展开后你应该能看到一个名为JSESSIONID的Cookie。点击它在右侧详情面板中仔细检查其属性名称/值应有值。域应为api.myapp.com。路径通常为/。过期时间根据后端设置。HttpOnly应有勾选安全。Secure必须有勾选。SameSite必须显示为None。验证请求在前端进行一个需要认证的API操作如获取用户信息。在“网络” (Network) 标签页中找到该请求。检查请求头应包含Cookie: JSESSIONID…。检查请求的Origin头应为https://www.myapp.com。检查响应头应包含access-control-allow-origin: https://www.myapp.com和access-control-allow-credentials: true。5.3 常见问题排查速查表当你遇到问题时可以按以下表格快速定位现象可能原因排查步骤登录成功但后续请求返回401Session Cookie未在跨域请求中发送1. 检查浏览器开发者工具中后端域名下的JSESSIONIDCookie的SameSite和Secure属性是否正确。2. 检查前端请求是否设置了withCredentials: trueAxios或credentials: ‘include’fetch。3. 检查网络请求的请求头中是否包含Cookie字段。预检请求OPTIONS失败返回403或CORS错误CORS配置不正确1. 检查后端allowedOrigins是否包含了前端的精确源不能是*。2. 检查allowedMethods是否包含了前端实际使用的HTTP方法特别是OPTIONS。3. 检查allowedHeaders是否包含了前端发送的自定义头如Authorization,X-Requested-With。生产环境正常但本地开发环境Cookie不生效开发环境代理或Cookie Domain问题1. 检查setupProxy.js中是否配置了cookieDomainRewrite。2. 检查前端开发服务器和后端API是否运行在localhost的不同端口并确保代理配置正确。3. 尝试在浏览器中直接访问后端本地地址看是否能设置Cookie以隔离前端代理问题。Chrome浏览器中Cookie被标记为“警告”Cookie设置不符合浏览器最新安全规范1. 确保SameSiteNone和Secure同时存在且正确。2. 避免使用IP地址作为域名设置Cookie尽量使用有效的域名。3. 确保后端服务是通过HTTPS访问的Secure属性要求HTTPS。只有部分浏览器有问题浏览器对SameSite默认值的处理差异记住Chrome/Edge/新版本Firefox默认将未指定的Cookie视为Lax。Safari等可能有不同策略。解决方案是统一的为你的Session Cookie显式、正确地设置SameSiteNone; Secure。6. 进阶考量与安全加固基础配置能解决90%的问题但对于一个追求稳健的生产系统我们还需要考虑更多。6.1 多环境配置管理不同环境开发、测试、生产的前后端域名不同CORS的allowedOrigins需要动态管理。推荐方案使用Spring Profile和环境变量# application.yml spring: profiles: active: activatedProperties # Maven/Gradle过滤 — # application-dev.yml cors: allowed-origins: - “http://localhost:3000 - “http://localhost:8080” # 有时直接访问后端也需要 allowed-origin-patterns: # 更灵活的模式匹配 - “http://localhost:[*]” — # application-prod.yml cors: allowed-origins: - “https://www.myapp.com” - “https://admin.myapp.com” # 可能有多套前端在Java配置中注入Configuration public class CorsConfig implements WebMvcConfigurer { Value(“${cors.allowed-origins}”) private String[] allowedOrigins; Value(“${cors.allowed-origin-patterns:}”) private String[] allowedOriginPatterns; Override public void addCorsMappings(CorsRegistry registry) { CorsRegistration registration registry.addMapping(“/api/**”) .allowedMethods(“*”) .allowCredentials(true); if (allowedOriginPatterns ! null allowedOriginPatterns.length 0) { registration.allowedOriginPatterns(allowedOriginPatterns); } else if (allowedOrigins ! null allowedOrigins.length 0) { registration.allowedOrigins(allowedOrigins); } } }6.2 结合JWT等无状态Token的混合方案对于纯RESTful API使用无状态的JWTJSON Web Token可以避免Session Cookie的跨域烦恼。Token通常放在Authorization请求头中不受SameSite策略影响。但这并不意味着可以忽略CORS配置前端源仍然需要被后端允许。混合架构思路短期会话使用SameSiteNone; Secure; HttpOnly的Session Cookie利用Servlet容器成熟的管理机制。长期身份如“记住我”使用Refresh Token存储在SameSiteStrict; Secure; HttpOnly的Cookie中仅在同站请求如刷新Token时发送并配合短期的Access Token放在内存或localStorage中通过Authorization头发送。这需要在Spring Security中配置复杂的OAuth2ResourceServer或自定义Filter链。安全警告如果将Access Token存储在localStorage中需警惕XSS攻击。HttpOnly的Cookie能有效防御XSS窃取Token但无法用于跨域。这是一个典型的安全与便利性的权衡。6.3 监控与告警在微服务或分布式架构中Cookie问题可能零星出现。建议添加监控日志监控在后端日志中对频繁出现的401/403状态码进行聚合告警并记录请求的Origin头和Cookie头信息注意脱敏便于分析是否是CORS或Cookie问题。前端监控在前端应用如使用Sentry中捕获并上报网络请求失败特别是状态码为401、403或0网络/CORS错误的请求。健康检查端点创建一个/api/health/auth端点前端定期调用携带凭证。该端点验证Session有效性并返回简单状态。前端可以根据此结果决定是否要引导用户重新认证。7. 总结与个人踩坑实录跨栈部署中的Cookie安全本质上是一场与浏览器安全模型不断演进的“合作”。SameSite策略的收紧是大势所趋目的是为了保护用户免受CSRF等攻击。作为开发者我们的任务不是对抗它而是理解它并正确配置我们的应用。回顾整个配置过程最核心的其实就是四步后端Cookie配置确保JSESSIONID等会话Cookie被设置为SameSiteNone; Secure; HttpOnly。后端CORS配置设置allowCredentials(true)和具体的前端allowedOrigins。前端请求配置在所有需要认证的请求中设置withCredentials: true。HTTPS环境生产环境必须使用HTTPS因为Secure属性是强制要求。我印象最深的一个坑是在一个旧项目迁移中发生的。后端升级到了Spring Boot 2.7按照文档配置了server.servlet.session.cookie.same-sitenone但上线后部分用户的Chrome浏览器依然无法登录。排查了半天才发现是因为项目里一个古老的、用于处理旧版IE的自定义Filter在输出响应时错误地重写了Set-Cookie头把SameSiteNone给覆盖掉了。这个教训告诉我当系统中有多个地方可能操作Cookie时一定要理清优先级和执行顺序。全局配置通常是最优解但要警惕自定义代码的“副作用”。另一个常见的误区是开发者只在登录接口关注Cookie却忘了其他API调用。请记住每一个从https://www.myapp.com发往https://api.myapp.com的、需要维持会话的请求都必须遵循这套规则。建议在项目初期就通过一个全局的Axios实例或请求拦截器来统一管理withCredentials而不是在每个请求里单独设置。最后浏览器的开发者工具是你最好的朋友。遇到认证问题第一件事就是打开“网络”和“应用”面板亲眼看看请求头、响应头和存储的Cookie到底长什么样。很多时候答案就清晰地写在里面。