跨域请求(CORS)中无法发送 Cookie 问题的完整指南

如果您遇到过这样的情况:服务器正确设置了 Cookie(在浏览器响应中可见),但在后续请求中,浏览器没有将该 Cookie 返回给服务器,那么本指南适合您。这个问题几乎总是发生在您项目的前端和后端位于两个不同的域或端口时 — 例如,前端在 app.example.com,API 在 api.example.com

为什么会发生这种情况?

现代浏览器为了防范 CSRF 攻击和会话劫持,实施了两项严格的安全策略:

  1. CORS 策略:默认情况下,浏览器不允许跨域请求携带凭据(如 Cookie),除非服务器明确允许。
  2. SameSite 策略:从 Chrome 80 开始,Cookie 默认被视为具有 SameSite=Lax 行为,这意味着它们不会在跨域请求中发送,除非明确设置为 SameSite=None

重要的关键是,要解决这个问题,客户端和服务器端都必须正确配置。仅修改一端是不够的。

第一步:正确配置服务器

服务器端的三个黄金法则

法则一 — 启用凭据:服务器响应头必须包含以下值:

Access-Control-Allow-Credentials: true

法则二 — 不使用通配符:当启用凭据时,您不能在 Access-Control-Allow-Origin 头中使用 * 通配符。您必须精确指定前端的完整域名地址:

Access-Control-Allow-Origin: https://app.example.com

法则三 — Cookie 属性:Cookie 必须设置两个属性:SameSite=NoneSecure。如果没有这两个属性,浏览器会存储 Cookie,但永远不会在跨域请求中发送它。

Node.js (Express) 示例代码

const express = require('express');
const cors = require('cors');

const app = express();

app.use(cors({
  origin: 'https://app.example.com', // 精确的前端域名,末尾不要 /
  credentials: true
}));

app.post('/login', (req, res) => {
  res.cookie('session_token', 'abc123', {
    httpOnly: true,   // 防止 JavaScript 访问 Cookie
    secure: true,     // 仅通过 HTTPS 发送
    sameSite: 'none', // 允许在跨域请求中发送
    maxAge: 24 * 60 * 60 * 1000
  });
  res.json({ message: '登录成功' });
});

PHP 示例代码

<?php
// 指定精确的前端域名,而不是 *
header("Access-Control-Allow-Origin: https://app.example.com");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");

// 响应预检请求
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

// 使用所需属性设置 Cookie
setcookie('session_token', 'abc123', [
    'expires'  => time() + 86400,
    'path'     => '/',
    'secure'   => true,      // 必需
    'httponly' => true,
    'samesite' => 'None'     // 跨域必需
]);

注意:如果您使用 Laravel,请在 config/cors.php 中将 supports_credentials 设置为 true,并将前端域名添加到 allowed_origins 数组中。

第二步:正确配置客户端(前端)

即使服务器配置完整,除非您在客户端明确声明此请求“带有凭据”,否则浏览器仍然不会发送 Cookie。

使用 Fetch API

fetch('https://api.example.com/user/profile', {
  method: 'GET',
  credentials: 'include'  // 这一行是解决问题的关键
})
  .then(res => res.json())
  .then(data => console.log(data));

使用 Axios

// 对于特定请求
axios.get('https://api.example.com/user/profile', {
  withCredentials: true
});

// 或者对整个项目全局设置
axios.defaults.withCredentials = true;

故障排除检查清单

如果在应用上述方法后问题仍然存在,请按顺序检查以下项目:

  • 双方都在 HTTPS 上吗? Secure 属性意味着 Cookie 仅在加密连接上工作。如果您的站点在 HTTP 上,浏览器将不会接受 Cookie。(例外:localhost 上的开发环境)
  • Origin 地址是否精确? Access-Control-Allow-Origin 地址必须与您在浏览器中看到的 URL 完全相同。即使是 www 的差异或末尾的斜杠 / 也会导致失败。
  • OPTIONS 请求是否成功? 在浏览器开发者工具的 Network 标签中,检查预检请求(使用 OPTIONS 方法)。此请求必须以 200 或 204 状态码和正确的 CORS 头响应。
  • Cookie 是否已存储? 在浏览器中,转到 DevTools ← Application ← Cookies,确保 Cookie 已使用 SecureSameSite=None 属性存储。
  • 是否有代理或中间防火墙? 有时 Cloudflare 或 Nginx 等 Web 服务器会重写头。请同时检查它们的配置。

总结

在跨域请求中发送 Cookie 需要三个因素的协调:服务器带有 Access-Control-Allow-Credentials: true 和精确的 Origin,Cookie 带有 SameSite=None; Secure 属性,以及客户端启用 credentials: 'include'withCredentials: true。遵循这三点,问题将完全解决。


可靠的基础设施,开发者安心的选择

正确实现 CORS 和 Cookie 管理只是故事的一部分;您的项目运行所在的基础设施在服务的稳定性和安全性方面也起着决定性作用:

Radib 主机 — 高速 Web 托管,完全支持免费 SSL 和标准头配置;是托管您项目前端和后端的理想选择。

Radib 虚拟服务器 — 如果您的项目需要完全访问权限、专用资源和 Web 服务器配置(Nginx/Apache)的自由,Radib 虚拟服务器凭借强大的硬件和稳定的网络,是专业 API 的最佳选择。

Radib 调试和安全服务 — 如果您正在处理复杂的 CORS 错误、身份验证问题或安全挑战,Radib 的技术团队在 Web 应用程序故障排除和安全方面拥有深厚经验,可以帮助您从根本上解决问题。

 

這篇文章有幫助嗎? 112 用戶發現這個有用 (112 投票)