Node.js与Koa跨域Cookie共享实战指南

Node.js与Koa跨域Cookie共享实战指南

一、跨域Cookie的核心挑战

在前后端分离架构中,跨域请求(域名不同或端口不同)时浏览器默认会阻止Cookie的自动传递。这主要源于浏览器的同源策略(Same-Origin Policy),该策略要求协议、域名和端口三者完全一致才允许共享Cookie等敏感数据。

典型场景示例

  • 前端:https://client.example.com:8080
  • 后端:https://api.example.com:8443
  • 需求:前端请求时自动携带认证Cookie

关键限制因素

  1. SameSite属性:现代浏览器默认将Cookie的SameSite属性设为Lax,严格限制跨站请求
  2. Secure标志:HTTPS环境下要求Cookie必须设置Secure标志
  3. HTTPOnly限制:涉及XSS防护时可能影响前端读取

二、Koa中间件配置方案

1. 基础CORS中间件配置

  1. const Koa = require('koa');
  2. const cors = require('@koa/cors');
  3. const app = new Koa();
  4. app.use(cors({
  5. origin: function(ctx) {
  6. // 动态允许指定域名
  7. const allowedOrigins = [
  8. 'https://client.example.com:8080',
  9. 'https://dev.client.example.com'
  10. ];
  11. const requestOrigin = ctx.request.header.origin;
  12. return allowedOrigins.includes(requestOrigin) ? requestOrigin : false;
  13. },
  14. credentials: true, // 关键配置:允许携带认证信息
  15. allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  16. exposeHeaders: ['Content-Length', 'X-Kustom-Header'],
  17. maxAge: 600 // 预检请求缓存时间
  18. }));

2. 自定义中间件增强控制

  1. app.use(async (ctx, next) => {
  2. // 设置响应头
  3. ctx.set('Access-Control-Allow-Origin', ctx.request.header.origin);
  4. ctx.set('Access-Control-Allow-Credentials', 'true');
  5. ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  6. // 处理OPTIONS预检请求
  7. if (ctx.method === 'OPTIONS') {
  8. ctx.status = 204;
  9. return;
  10. }
  11. await next();
  12. });

三、Cookie属性深度配置

1. 服务器端设置规范

  1. app.use(async ctx => {
  2. // 设置可跨域的Cookie
  3. ctx.cookies.set('session_id', 'abc123', {
  4. domain: '.example.com', // 允许子域名访问
  5. path: '/',
  6. secure: true, // 必须HTTPS
  7. httpOnly: true, // 防止XSS攻击
  8. sameSite: 'none', // 关键:允许跨站
  9. maxAge: 86400000 // 24小时有效期
  10. });
  11. ctx.body = { success: true };
  12. });

2. 属性组合策略

属性 推荐值 作用说明
SameSite none 必须显式设置才能跨域
Secure true 强制HTTPS传输
HttpOnly true 防止JavaScript访问
Domain .example.com 允许子域名共享

四、前端请求配置要点

1. Fetch API配置

  1. fetch('https://api.example.com/data', {
  2. method: 'GET',
  3. credentials: 'include', // 关键配置:携带Cookie
  4. headers: {
  5. 'Content-Type': 'application/json'
  6. }
  7. })
  8. .then(response => response.json())
  9. .then(data => console.log(data));

2. Axios配置示例

  1. const instance = axios.create({
  2. baseURL: 'https://api.example.com',
  3. withCredentials: true // 关键配置
  4. });
  5. instance.get('/data')
  6. .then(response => console.log(response.data));

五、安全增强方案

1. CSRF防护机制

  1. // 生成CSRF Token中间件
  2. app.use(async (ctx, next) => {
  3. const token = ctx.cookies.get('csrf_token') ||
  4. require('crypto').randomBytes(32).toString('hex');
  5. ctx.cookies.set('csrf_token', token, {
  6. secure: true,
  7. httpOnly: false, // 需要前端读取
  8. sameSite: 'strict'
  9. });
  10. await next();
  11. });
  12. // 验证中间件
  13. app.use(async (ctx, next) => {
  14. const method = ctx.method;
  15. if (['POST', 'PUT', 'DELETE'].includes(method)) {
  16. const token = ctx.request.body.csrf_token ||
  17. ctx.request.header['x-csrf-token'];
  18. if (token !== ctx.cookies.get('csrf_token')) {
  19. ctx.throw(403, 'Invalid CSRF token');
  20. }
  21. }
  22. await next();
  23. });

2. 动态域名白名单

  1. const allowedDomains = new Set([
  2. 'client.example.com',
  3. 'dev.client.example.com',
  4. 'staging.client.example.com'
  5. ]);
  6. app.use(async (ctx, next) => {
  7. const origin = ctx.request.header.origin;
  8. if (!origin) return await next();
  9. try {
  10. const url = new URL(origin);
  11. if (!allowedDomains.has(url.hostname)) {
  12. ctx.throw(403, 'Origin not allowed');
  13. }
  14. ctx.set('Access-Control-Allow-Origin', origin);
  15. } catch (e) {
  16. ctx.throw(400, 'Invalid origin');
  17. }
  18. await next();
  19. });

六、常见问题解决方案

1. 浏览器控制台报错”Credential is not supported”

原因:未同时设置credentials: truesameSite: none+secure: true

解决方案

  1. // 确保中间件同时包含
  2. app.use(cors({
  3. origin: true, // 动态获取origin
  4. credentials: true
  5. }));
  6. // 设置Cookie时
  7. ctx.cookies.set('token', 'xxx', {
  8. sameSite: 'none',
  9. secure: true
  10. });

2. 子域名无法共享Cookie

原因:Domain属性设置错误

解决方案

  1. // 正确设置方式(注意前面的点)
  2. ctx.cookies.set('session', '123', {
  3. domain: '.example.com', // 允许所有子域名
  4. path: '/'
  5. });

七、性能优化建议

  1. 预检请求缓存:设置合理的maxAge减少OPTIONS请求
  2. Cookie大小控制:保持Cookie在4KB以内,避免影响性能
  3. HTTP/2优化:启用HTTP/2减少头部开销
  4. Service Worker缓存:对静态资源使用Service Worker缓存

八、完整示例项目结构

  1. project/
  2. ├── src/
  3. ├── middleware/
  4. ├── cors.js # 自定义CORS中间件
  5. └── csrf.js # CSRF防护中间件
  6. ├── routes/
  7. └── api.js # API路由
  8. └── app.js # 主应用文件
  9. ├── config/
  10. └── security.js # 安全配置
  11. └── package.json

九、生产环境部署检查清单

  1. 确认所有Cookie设置secure: true
  2. 验证CSRF防护机制有效
  3. 测试不同子域名的Cookie共享
  4. 检查HTTPS证书有效性
  5. 监控跨域请求的错误日志
  6. 定期更新CORS白名单

十、进阶方案:JWT替代方案

对于复杂场景,可考虑使用JWT替代Cookie:

  1. // 生成Token
  2. const jwt = require('jsonwebtoken');
  3. const token = jwt.sign({ userId: 123 }, 'secret', { expiresIn: '1h' });
  4. // 返回Token
  5. ctx.body = { token };
  6. // 验证中间件
  7. app.use(async (ctx, next) => {
  8. const token = ctx.request.header.authorization?.split(' ')[1];
  9. try {
  10. const decoded = jwt.verify(token, 'secret');
  11. ctx.state.user = decoded;
  12. await next();
  13. } catch (e) {
  14. ctx.throw(401, 'Invalid token');
  15. }
  16. });

结语

实现跨域Cookie共享需要综合考虑浏览器安全策略、服务器配置和前端请求设置。通过合理配置Koa的CORS中间件、精确设置Cookie属性,并配合必要的安全防护机制,可以构建安全可靠的跨域认证系统。建议在实际项目中结合具体业务场景进行测试和调优,确保在安全性和用户体验之间取得平衡。