Node.js与Koa跨域Cookies实战指南

Node.js与Koa实现跨域(域名/端口不同)携带Cookies的完整方案

一、跨域场景与Cookies的特殊性

在前后端分离架构中,跨域请求是常见需求。当涉及不同域名(如api.example.comwww.example.com)或不同端口(如localhost:3000localhost:8080)时,浏览器默认会阻止携带Cookies,这会导致需要身份验证的API请求失败。

关键矛盾点

  • 浏览器同源策略限制:协议、域名、端口三者必须完全相同
  • Cookies的SameSite属性限制:现代浏览器默认设置为LaxStrict
  • 安全策略冲突:需要同时满足CORS规范和Cookies安全要求

二、Koa框架的CORS中间件配置

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://client.example.com:8080', // 明确指定允许的源
  6. credentials: true, // 关键配置:允许携带凭证
  7. allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  8. exposeHeaders: ['Authorization']
  9. }));

配置要点

  • origin不能使用通配符*,必须明确指定完整域名
  • credentials: true是允许携带Cookies的必要条件
  • 建议同时配置allowMethodsexposeHeaders

2. 动态源配置方案

对于需要支持多域名的情况,可实现动态白名单:

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

三、Cookies安全配置

1. 服务端设置Cookies

关键属性配置示例:

  1. app.use(async (ctx) => {
  2. ctx.cookies.set('session_id', 'abc123', {
  3. domain: '.example.com', // 子域名共享
  4. secure: true, // 仅HTTPS传输
  5. httpOnly: true, // 防止XSS攻击
  6. sameSite: 'none', // 允许跨域携带
  7. maxAge: 86400000 // 24小时有效期
  8. });
  9. ctx.body = 'Cookies set successfully';
  10. });

属性详解

  • sameSite: 'none'必须与secure: true同时使用
  • domain设置为顶级域名可实现子域名共享
  • 生产环境必须启用secure属性

2. 客户端请求配置

前端请求需设置withCredentials

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

四、完整Koa服务实现

1. 项目结构

  1. /project
  2. ├── server.js
  3. ├── config.js
  4. └── package.json

2. 核心代码实现

server.js:

  1. const Koa = require('koa');
  2. const cors = require('@koa/cors');
  3. const Router = require('@koa/router');
  4. const app = new Koa();
  5. const router = new Router();
  6. // 安全中间件
  7. app.use(async (ctx, next) => {
  8. try {
  9. await next();
  10. } catch (err) {
  11. ctx.status = err.status || 500;
  12. ctx.body = { error: err.message };
  13. }
  14. });
  15. // CORS配置
  16. app.use(cors({
  17. origin: function(ctx) {
  18. const allowed = ['http://localhost:8080', 'https://client.example.com'];
  19. return allowed.includes(ctx.headers.origin) ? ctx.headers.origin : false;
  20. },
  21. credentials: true
  22. }));
  23. // 路由配置
  24. router.get('/api/data', async (ctx) => {
  25. // 检查Cookie
  26. const sessionId = ctx.cookies.get('session_id');
  27. if (!sessionId) {
  28. ctx.throw(401, 'Unauthorized');
  29. }
  30. ctx.body = {
  31. data: 'Secure data',
  32. session: sessionId
  33. };
  34. });
  35. app.use(router.routes());
  36. app.use(router.allowedMethods());
  37. const PORT = process.env.PORT || 3000;
  38. app.listen(PORT, () => {
  39. console.log(`Server running on port ${PORT}`);
  40. });

package.json依赖:

  1. {
  2. "dependencies": {
  3. "@koa/cors": "^3.1.0",
  4. "@koa/router": "^10.1.1",
  5. "koa": "^2.13.4"
  6. }
  7. }

五、常见问题解决方案

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

原因

  • 同时设置了sameSite: 'none'但未设置secure: true
  • 使用了HTTP协议而非HTTPS

解决方案

  1. // 错误配置
  2. ctx.cookies.set('token', 'xxx', { sameSite: 'none' });
  3. // 正确配置
  4. ctx.cookies.set('token', 'xxx', {
  5. sameSite: 'none',
  6. secure: true, // 必须
  7. httpOnly: true
  8. });

2. 前端请求未携带Cookies

检查清单

  1. 确认请求URL与CORS配置的origin匹配
  2. 检查是否设置了withCredentials: true
  3. 验证Cookies的domainpath属性
  4. 使用浏览器开发者工具查看Network标签中的请求头

3. 生产环境HTTPS配置

推荐使用Let’s Encrypt免费证书:

  1. # 使用Certbot获取证书
  2. sudo certbot certonly --manual -d api.example.com

Nginx反向代理配置示例:

  1. server {
  2. listen 443 ssl;
  3. server_name api.example.com;
  4. ssl_certificate /path/to/fullchain.pem;
  5. ssl_certificate_key /path/to/privkey.pem;
  6. location / {
  7. proxy_pass http://localhost:3000;
  8. proxy_set_header Host $host;
  9. proxy_set_header X-Real-IP $remote_addr;
  10. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  11. proxy_set_header X-Forwarded-Proto $scheme;
  12. }
  13. }

六、安全最佳实践

  1. 严格限制允许的源

    • 避免使用通配符*
    • 定期更新允许的域名列表
  2. Cookies安全属性

    1. ctx.cookies.set('auth_token', 'xxx', {
    2. httpOnly: true,
    3. secure: process.env.NODE_ENV === 'production',
    4. sameSite: 'lax', // 开发环境可使用lax
    5. maxAge: 3600000 // 1小时
    6. });
  3. CSRF防护

    • 实现双重提交Cookie机制
    • 或使用CSP头增强防护
  4. 监控与日志

    • 记录跨域请求日志
    • 监控异常的跨域访问模式

七、性能优化建议

  1. CDN加速

    • 将静态资源部署到CDN
    • 配置CDN的CORS规则
  2. HTTP/2支持

    1. const http2 = require('http2');
    2. const fs = require('fs');
    3. const server = http2.createSecureServer({
    4. key: fs.readFileSync('/path/to/privkey.pem'),
    5. cert: fs.readFileSync('/path/to/fullchain.pem')
    6. }, app.callback());
  3. 连接复用

    • 配置Keep-Alive头
    • 设置合理的超时时间

八、测试验证方法

  1. 使用cURL测试

    1. curl -v -H "Origin: http://client.example.com:8080" \
    2. -H "Cookie: session_id=abc123" \
    3. --cookie "session_id=abc123" \
    4. http://localhost:3000/api/data
  2. Postman测试

    • 在Headers中添加Origin
    • 在Cookies管理中添加测试Cookie
  3. 自动化测试脚本

    1. const axios = require('axios');
    2. const https = require('https');
    3. // 忽略SSL证书验证(仅测试环境)
    4. const agent = new https.Agent({ rejectUnauthorized: false });
    5. async function testCrossOrigin() {
    6. try {
    7. const response = await axios.get('https://api.example.com/api/data', {
    8. withCredentials: true,
    9. httpsAgent: agent
    10. });
    11. console.log('Test passed:', response.data);
    12. } catch (error) {
    13. console.error('Test failed:', error.response?.data || error.message);
    14. }
    15. }
    16. testCrossOrigin();

九、总结与展望

实现Node.js + Koa的跨域Cookies支持需要综合考虑:

  1. 精确的CORS配置
  2. 安全的Cookies属性设置
  3. 前后端协同配置
  4. 生产环境的安全加固

未来发展方向:

  • Service Worker的跨域处理
  • WebAssembly与跨域交互
  • HTTP/3协议对跨域的影响
  • 浏览器新安全策略的适配

通过遵循本文的实践方案,开发者可以构建安全、可靠的跨域身份验证系统,满足现代Web应用的需求。建议定期审查安全配置,关注浏览器安全策略的更新,确保系统的持续安全性。