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中间件(推荐)或手动设置响应头:
const Koa = require('koa');const cors = require('@koa/cors');const app = new Koa();// 方法1:使用@koa/cors(推荐)app.use(cors({origin: 'https://example.com', // 明确指定允许的域名credentials: true, // 允许携带凭证allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],exposeHeaders: ['Content-Length', 'X-Kustom-Header']}));// 方法2:手动设置响应头(等效实现)app.use(async (ctx, next) => {ctx.set('Access-Control-Allow-Origin', 'https://example.com');ctx.set('Access-Control-Allow-Credentials', 'true');ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');if (ctx.method === 'OPTIONS') {ctx.status = 204;} else {await next();}});
2.2 动态域名处理方案
对于需要支持多域名的场景,可通过请求头动态设置:
const ALLOWED_ORIGINS = ['https://example.com','https://another-domain.com'];app.use(async (ctx, next) => {const origin = ctx.headers.origin;if (ALLOWED_ORIGINS.includes(origin)) {ctx.set('Access-Control-Allow-Origin', origin);ctx.set('Access-Control-Allow-Credentials', 'true');}await next();});
三、Cookies的完整属性配置
3.1 服务器端设置Cookies
使用koa-cookie或手动设置Set-Cookie头:
const cookie = require('koa-cookie').default;app.use(cookie());app.use(async (ctx) => {// 方法1:使用koa-cookiectx.cookies.set('token', 'abc123', {domain: '.example.com', // 允许子域名访问path: '/', // 允许所有路径访问maxAge: 86400000, // 24小时过期httpOnly: true, // 禁止JS访问secure: true, // HTTPS传输sameSite: 'none' // 允许跨域发送});// 方法2:手动设置Set-Cookiectx.set('Set-Cookie', ['token=abc123; Domain=.example.com; Path=/; Max-Age=86400; HttpOnly; Secure; SameSite=None']);});
3.2 客户端请求配置
前端发起请求时必须设置withCredentials:
// Fetch API示例fetch('https://api.example.com/data', {method: 'GET',credentials: 'include' // 关键配置}).then(response => response.json()).then(data => console.log(data));// Axios示例axios.get('https://api.example.com/data', {withCredentials: true // 关键配置}).then(response => console.log(response.data));
四、安全实践与常见问题
4.1 安全增强措施
- HTTPS强制:所有携带凭证的请求必须通过HTTPS传输
- CSRF防护:结合CSRF Token使用双重验证
- Cookie作用域限制:
- 精确设置
Domain和Path - 避免使用顶级域名(如
.com)
- 精确设置
- 短期有效期:设置合理的
Max-Age
4.2 常见问题解决方案
问题1:Cookies未被携带
- 检查
withCredentials是否设置为true - 确认
Access-Control-Allow-Origin不是* - 验证
SameSite=None和Secure属性是否设置
问题2:预检请求失败
- 确保OPTIONS请求返回204状态码
- 检查
Access-Control-Allow-Methods和Access-Control-Allow-Headers是否包含所需值
问题3:Chrome 80+ SameSite问题
- Chrome 80+默认将未明确指定
SameSite的Cookies视为SameSite=Lax - 必须显式设置
SameSite=None; Secure才能跨域发送
五、完整示例代码
const Koa = require('koa');const cors = require('@koa/cors');const cookie = require('koa-cookie').default;const app = new Koa();const PORT = 3000;// 中间件配置app.use(cookie());app.use(cors({origin: 'https://client.example.com',credentials: true,allowMethods: ['GET', 'POST']}));// 路由处理app.use(async (ctx) => {if (ctx.path === '/login') {ctx.cookies.set('auth_token', 'secure-token-123', {httpOnly: true,secure: true,sameSite: 'none',maxAge: 3600000});ctx.body = { message: 'Login successful' };} else if (ctx.path === '/data') {const token = ctx.cookies.get('auth_token');if (!token) {ctx.status = 401;ctx.body = { error: 'Unauthorized' };} else {ctx.body = { data: 'Sensitive information', token };}} else {ctx.status = 404;ctx.body = { error: 'Not found' };}});// 启动服务app.listen(PORT, () => {console.log(`Server running on http://localhost:${PORT}`);});
六、生产环境建议
- 环境分离:开发/测试/生产环境使用不同的Cookie域名和密钥
- 密钥轮换:定期更换Cookie签名密钥
- 监控告警:对异常的跨域请求进行监控
- 文档规范:明确记录允许的跨域域名列表和安全要求
- 定期审计:每季度进行安全配置审计
通过以上配置,开发者可以在Node.js + Koa环境中安全地实现跨域Cookies携带,同时满足现代浏览器的安全要求。实际部署时建议结合具体业务场景进行适当调整,并始终遵循最小权限原则。