Node.js + Koa跨域携带Cookies全攻略

Node.js + Koa实现跨域(域名不同,端口不同)携带Cookies全攻略

一、跨域与Cookies携带的基础原理

1.1 跨域的本质与限制

跨域请求(Cross-Origin Resource Sharing)指当前页面的协议、域名或端口与请求资源不一致时触发的安全限制。浏览器默认会阻止跨域请求携带认证信息(如Cookies、HTTP认证等),这是Web安全模型的核心设计。

1.2 Cookies的跨域携带条件

要实现跨域携带Cookies,必须同时满足以下条件:

  • 服务器设置Access-Control-Allow-Credentials: true
  • 客户端请求必须携带withCredentials: true
  • 服务器返回的Access-Control-Allow-Origin不能为*,必须明确指定允许的域名
  • Cookies必须设置SameSite=None(Chrome 80+要求)和Secure属性(HTTPS环境下)

二、Koa中间件配置详解

2.1 基础CORS中间件配置

使用@koa/cors中间件(推荐)或手动设置响应头:

  1. const Koa = require('koa');
  2. const cors = require('@koa/cors');
  3. const app = new Koa();
  4. // 方法1:使用@koa/cors(推荐)
  5. app.use(cors({
  6. origin: 'https://example.com', // 明确指定允许的域名
  7. credentials: true, // 允许携带凭证
  8. allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  9. exposeHeaders: ['Content-Length', 'X-Kustom-Header']
  10. }));
  11. // 方法2:手动设置响应头(等效实现)
  12. app.use(async (ctx, next) => {
  13. ctx.set('Access-Control-Allow-Origin', 'https://example.com');
  14. ctx.set('Access-Control-Allow-Credentials', 'true');
  15. ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  16. ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  17. if (ctx.method === 'OPTIONS') {
  18. ctx.status = 204;
  19. } else {
  20. await next();
  21. }
  22. });

2.2 动态域名处理方案

对于需要支持多域名的场景,可通过请求头动态设置:

  1. const ALLOWED_ORIGINS = [
  2. 'https://example.com',
  3. 'https://another-domain.com'
  4. ];
  5. app.use(async (ctx, next) => {
  6. const origin = ctx.headers.origin;
  7. if (ALLOWED_ORIGINS.includes(origin)) {
  8. ctx.set('Access-Control-Allow-Origin', origin);
  9. ctx.set('Access-Control-Allow-Credentials', 'true');
  10. }
  11. await next();
  12. });

三、Cookies的完整属性配置

3.1 服务器端设置Cookies

使用koa-cookie或手动设置Set-Cookie头:

  1. const cookie = require('koa-cookie').default;
  2. app.use(cookie());
  3. app.use(async (ctx) => {
  4. // 方法1:使用koa-cookie
  5. ctx.cookies.set('token', 'abc123', {
  6. domain: '.example.com', // 允许子域名访问
  7. path: '/', // 允许所有路径访问
  8. maxAge: 86400000, // 24小时过期
  9. httpOnly: true, // 禁止JS访问
  10. secure: true, // HTTPS传输
  11. sameSite: 'none' // 允许跨域发送
  12. });
  13. // 方法2:手动设置Set-Cookie
  14. ctx.set('Set-Cookie', [
  15. 'token=abc123; Domain=.example.com; Path=/; Max-Age=86400; HttpOnly; Secure; SameSite=None'
  16. ]);
  17. });

3.2 客户端请求配置

前端发起请求时必须设置withCredentials

  1. // Fetch API示例
  2. fetch('https://api.example.com/data', {
  3. method: 'GET',
  4. credentials: 'include' // 关键配置
  5. })
  6. .then(response => response.json())
  7. .then(data => console.log(data));
  8. // Axios示例
  9. axios.get('https://api.example.com/data', {
  10. withCredentials: true // 关键配置
  11. })
  12. .then(response => console.log(response.data));

四、安全实践与常见问题

4.1 安全增强措施

  1. HTTPS强制:所有携带凭证的请求必须通过HTTPS传输
  2. CSRF防护:结合CSRF Token使用双重验证
  3. Cookie作用域限制
    • 精确设置DomainPath
    • 避免使用顶级域名(如.com
  4. 短期有效期:设置合理的Max-Age

4.2 常见问题解决方案

问题1:Cookies未被携带

  • 检查withCredentials是否设置为true
  • 确认Access-Control-Allow-Origin不是*
  • 验证SameSite=NoneSecure属性是否设置

问题2:预检请求失败

  • 确保OPTIONS请求返回204状态码
  • 检查Access-Control-Allow-MethodsAccess-Control-Allow-Headers是否包含所需值

问题3:Chrome 80+ SameSite问题

  • Chrome 80+默认将未明确指定SameSite的Cookies视为SameSite=Lax
  • 必须显式设置SameSite=None; Secure才能跨域发送

五、完整示例代码

  1. const Koa = require('koa');
  2. const cors = require('@koa/cors');
  3. const cookie = require('koa-cookie').default;
  4. const app = new Koa();
  5. const PORT = 3000;
  6. // 中间件配置
  7. app.use(cookie());
  8. app.use(cors({
  9. origin: 'https://client.example.com',
  10. credentials: true,
  11. allowMethods: ['GET', 'POST']
  12. }));
  13. // 路由处理
  14. app.use(async (ctx) => {
  15. if (ctx.path === '/login') {
  16. ctx.cookies.set('auth_token', 'secure-token-123', {
  17. httpOnly: true,
  18. secure: true,
  19. sameSite: 'none',
  20. maxAge: 3600000
  21. });
  22. ctx.body = { message: 'Login successful' };
  23. } else if (ctx.path === '/data') {
  24. const token = ctx.cookies.get('auth_token');
  25. if (!token) {
  26. ctx.status = 401;
  27. ctx.body = { error: 'Unauthorized' };
  28. } else {
  29. ctx.body = { data: 'Sensitive information', token };
  30. }
  31. } else {
  32. ctx.status = 404;
  33. ctx.body = { error: 'Not found' };
  34. }
  35. });
  36. // 启动服务
  37. app.listen(PORT, () => {
  38. console.log(`Server running on http://localhost:${PORT}`);
  39. });

六、生产环境建议

  1. 环境分离:开发/测试/生产环境使用不同的Cookie域名和密钥
  2. 密钥轮换:定期更换Cookie签名密钥
  3. 监控告警:对异常的跨域请求进行监控
  4. 文档规范:明确记录允许的跨域域名列表和安全要求
  5. 定期审计:每季度进行安全配置审计

通过以上配置,开发者可以在Node.js + Koa环境中安全地实现跨域Cookies携带,同时满足现代浏览器的安全要求。实际部署时建议结合具体业务场景进行适当调整,并始终遵循最小权限原则。