Node.js + Koa跨域携带Cookies实战指南

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

一、跨域场景与Cookie安全挑战

在前后端分离架构中,跨域请求(不同域名或端口)是常见场景。当需要携带身份验证的Cookie时,浏览器会因同源策略拦截请求。例如前端部署在http://frontend.com:3000,后端API在http://api.example.com:8000,常规请求会丢失Cookie。

1.1 跨域通信原理

浏览器通过HTTP请求头中的Origin字段标识来源,服务器响应时需通过Access-Control-Allow-*系列头明确允许跨域。当涉及Cookie时,需额外处理credentials模式。

1.2 Cookie安全三要素

实现跨域Cookie传递需同时满足:

  • 服务器设置Access-Control-Allow-Credentials: true
  • 响应头包含Access-Control-Allow-Origin(不能为*
  • Cookie属性包含SameSite=None; Secure

二、Koa中间件配置方案

2.1 基础CORS中间件

使用@koa/cors中间件简化配置:

  1. const Koa = require('koa');
  2. const cors = require('@koa/cors');
  3. const app = new Koa();
  4. app.use(cors({
  5. origin: 'http://frontend.com:3000', // 精确匹配前端域名
  6. credentials: true, // 允许携带凭证
  7. allowMethods: ['GET', 'POST', 'PUT', 'DELETE']
  8. }));

2.2 自定义CORS中间件(进阶)

当需要动态控制时,可自定义中间件:

  1. app.use(async (ctx, next) => {
  2. const allowedOrigins = [
  3. 'http://frontend.com:3000',
  4. 'https://staging.frontend.com'
  5. ];
  6. const origin = ctx.headers.origin;
  7. if (allowedOrigins.includes(origin)) {
  8. ctx.set('Access-Control-Allow-Origin', origin);
  9. ctx.set('Access-Control-Allow-Credentials', 'true');
  10. ctx.set('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE');
  11. ctx.set('Access-Control-Allow-Headers', 'Content-Type,Authorization');
  12. }
  13. await next();
  14. });

三、Cookie属性深度配置

3.1 服务器端设置Cookie

使用koa-cookie中间件设置安全Cookie:

  1. const cookie = require('koa-cookie');
  2. app.use(cookie());
  3. // 设置跨域Cookie
  4. app.use(async (ctx) => {
  5. ctx.cookies.set('auth_token', 'xyz123', {
  6. domain: '.example.com', // 父域名共享
  7. path: '/',
  8. secure: true, // 必须HTTPS
  9. sameSite: 'none', // 允许跨域
  10. httpOnly: true, // 防止XSS
  11. maxAge: 86400000 // 24小时有效期
  12. });
  13. });

3.2 前端请求配置

Axios请求需设置withCredentials

  1. axios.get('https://api.example.com/data', {
  2. withCredentials: true
  3. }).then(response => {
  4. console.log(response.data);
  5. });

四、安全验证与最佳实践

4.1 双重验证机制

  1. CSRF Token验证:在Cookie中存储CSRF Token,表单提交时校验
  2. JWT签名验证:结合JWT实现无状态认证
    1. // JWT中间件示例
    2. const jwt = require('jsonwebtoken');
    3. app.use(async (ctx, next) => {
    4. const token = ctx.cookies.get('auth_token');
    5. try {
    6. const decoded = jwt.verify(token, process.env.JWT_SECRET);
    7. ctx.state.user = decoded;
    8. await next();
    9. } catch (err) {
    10. ctx.status = 401;
    11. ctx.body = { error: 'Unauthorized' };
    12. }
    13. });

4.2 HTTPS强制配置

生产环境必须启用HTTPS:

  1. const https = require('https');
  2. const fs = require('fs');
  3. const options = {
  4. key: fs.readFileSync('private-key.pem'),
  5. cert: fs.readFileSync('certificate.pem')
  6. };
  7. https.createServer(options, app.callback()).listen(443);

五、常见问题解决方案

5.1 预检请求(OPTIONS)处理

浏览器会在实际请求前发送OPTIONS预检请求,需确保服务器正确处理:

  1. app.use(async (ctx, next) => {
  2. if (ctx.method === 'OPTIONS') {
  3. ctx.set('Access-Control-Allow-Origin', ctx.headers.origin);
  4. ctx.set('Access-Control-Allow-Credentials', 'true');
  5. ctx.status = 204; // No Content
  6. return;
  7. }
  8. await next();
  9. });

5.2 子域名Cookie共享

设置domain属性实现父域名共享:

  1. // 设置可在所有子域名共享的Cookie
  2. ctx.cookies.set('session_id', 'abc456', {
  3. domain: '.example.com', // 包括api.example.com, admin.example.com等
  4. sameSite: 'none',
  5. secure: true
  6. });

六、完整实现示例

6.1 服务器端完整代码

  1. const Koa = require('koa');
  2. const cors = require('@koa/cors');
  3. const cookie = require('koa-cookie');
  4. const jwt = require('jsonwebtoken');
  5. const app = new Koa();
  6. // 中间件配置
  7. app.use(cookie());
  8. app.use(cors({
  9. origin: 'http://frontend.com:3000',
  10. credentials: true
  11. }));
  12. // 模拟登录接口
  13. app.use(async (ctx) => {
  14. if (ctx.path === '/login' && ctx.method === 'POST') {
  15. // 生成JWT Token
  16. const token = jwt.sign(
  17. { userId: '123', role: 'admin' },
  18. process.env.JWT_SECRET,
  19. { expiresIn: '1h' }
  20. );
  21. // 设置安全Cookie
  22. ctx.cookies.set('auth_token', token, {
  23. httpOnly: true,
  24. secure: process.env.NODE_ENV === 'production',
  25. sameSite: 'none',
  26. maxAge: 3600000
  27. });
  28. ctx.body = { success: true };
  29. }
  30. });
  31. app.listen(8000, () => {
  32. console.log('Server running on http://localhost:8000');
  33. });

6.2 前端调用示例

  1. // 登录请求
  2. async function login() {
  3. const response = await fetch('https://api.example.com/login', {
  4. method: 'POST',
  5. credentials: 'include',
  6. headers: {
  7. 'Content-Type': 'application/json'
  8. },
  9. body: JSON.stringify({ username: 'admin', password: '123456' })
  10. });
  11. if (response.ok) {
  12. // 登录成功后获取受保护数据
  13. const data = await fetch('https://api.example.com/protected', {
  14. credentials: 'include'
  15. });
  16. console.log(await data.json());
  17. }
  18. }

七、性能优化建议

  1. Cookie大小控制:单个域名Cookie总量建议不超过4KB
  2. Session存储优化:敏感数据存放在JWT中,非敏感数据使用Cookie
  3. CDN加速:对静态资源使用CDN分发,减少主域名压力
  4. HTTP/2启用:提升多资源加载性能

八、安全审计清单

  1. 验证所有跨域请求是否包含withCredentials
  2. 检查Cookie是否设置HttpOnlySecure标志
  3. 确认JWT签名密钥定期轮换
  4. 审计允许的跨域域名列表
  5. 监控异常的跨域请求频率

通过以上系统化的配置和验证,开发者可以在Node.js + Koa环境中实现安全可靠的跨域Cookie传递,为前后端分离架构提供坚实的身份验证基础。实际部署时,建议结合具体业务场景进行安全加固和性能调优。