Node系列 · Express:跨域(CORS)
Node系列 · Express跨域CORS前后端分离项目最常遇到的报错就是 CORS——浏览器拒绝跨域响应。本章讲清楚 CORS 的工作原理、Express 配置、不同场景的处理方式。一、跨域问题的本质页面http://localhost:5173 接口http://localhost:3000/api/users协议相同、域名相同、端口不同——不同源浏览器拦截响应。$curlhttp://localhost:3000/api/users[200OK][{id:1},{id:2}]# curl 直接成功浏览器失败# 浏览器 DevTools# Access to fetch ... has been blocked by CORS policycurl 不受浏览器同源策略约束——只有浏览器才会拦截。二、CORS 工作原理CORS 通过 HTTP 响应头声明允许谁访问HTTP/1.1 200 OK Access-Control-Allow-Origin: https://app.example.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Allow-Credentials: true Access-Control-Max-Age: 864002.1 简单请求 vs 预检请求浏览器按请求类型分两种简单请求非简单请求跨域请求请求类型直接发送检查响应头先发 OPTIONS 预检服务器同意后再发真实请求类型触发条件简单请求GET/HEAD/POST且不带自定义头 Content-Type 是application/x-www-form-urlencoded/multipart/form-data/text/plain预检请求其他所有情况PUT/DELETE/ 自定义头 /application/json等::: tip前端用fetch/axios发送application/json的 POST 请求时会先发 OPTIONS 预检。这是 CORS 配置错误的常见原因。:::三、CORS 关键响应头响应头作用Access-Control-Allow-Origin允许的源*或具体域名Access-Control-Allow-Methods允许的 HTTP 方法Access-Control-Allow-Headers允许的请求头Access-Control-Allow-Credentials是否允许携带 CookieAccess-Control-Max-Age预检结果缓存秒数Access-Control-Expose-Headers浏览器可读取的响应头白名单四、简单请求处理简单请求浏览器直接发服务器响应头加Access-Control-Allow-Origin即可app.use((req, res, next) { res.setHeader(Access-Control-Allow-Origin, https://app.example.com); next(); }); app.get(/api/users, (req, res) { res.json([{ id: 1, name: Alice }]); });4.1 通配符*res.setHeader(Access-Control-Allow-Origin, *); // 允许所有源::: warning带 Cookie 时不能用*——Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin: *是非法组合浏览器会拒绝。:::五、预检请求处理非简单请求浏览器先发 OPTIONS 探测OPTIONS /api/users HTTP/1.1 Origin: https://app.example.com Access-Control-Request-Method: POST Access-Control-Request-Headers: Content-Type, Authorization服务器必须响应这个 OPTIONS 请求app.options(*, (req, res) { res.setHeader(Access-Control-Allow-Origin, https://app.example.com); res.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); res.setHeader(Access-Control-Allow-Headers, Content-Type, Authorization); res.setHeader(Access-Control-Max-Age, 86400); // 缓存 24 小时 res.sendStatus(204); });5.1Access-Control-Max-Age预检结果会被浏览器缓存默认5 秒Chrome设Access-Control-Max-Age: 86400缓存 24 小时期间同源同方法同头的请求不再触发预检减少 OPTIONS 请求对服务器的压力。六、携带 Cookie / 认证信息app.use((req, res, next) { res.setHeader(Access-Control-Allow-Origin, https://app.example.com); res.setHeader(Access-Control-Allow-Credentials, true); // 允许 Cookie res.setHeader(Access-Control-Allow-Headers, Content-Type, Authorization); next(); });客户端要配合// fetch fetch(https://api.example.com/users, { credentials: include, // 携带 Cookie }); // axios axios.get(https://api.example.com/users, { withCredentials: true, }); // XMLHttpRequest xhr.withCredentials true;6.1 三条铁律条件必须服务端开Allow-Credentials: trueOrigin不能为*必须具体域名客户端用credentials: include浏览器才会带上 Cookie浏览器看到Allow-Credentials: true严格匹配 Origin不允许通配符七、动态 Origin 白名单生产环境通常按需放行多个域名const ALLOWED_ORIGINS [ https://app.example.com, https://admin.example.com, http://localhost:5173, // 开发环境 ]; app.use((req, res, next) { const origin req.headers.origin; if (ALLOWED_ORIGINS.includes(origin)) { res.setHeader(Access-Control-Allow-Origin, origin); res.setHeader(Access-Control-Allow-Credentials, true); res.setHeader(Vary, Origin); // 关键防止 CDN 缓存跨域头 } res.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); res.setHeader(Access-Control-Allow-Headers, Content-Type, Authorization); next(); });::: tipVary: Origin必须加——当多个 Origin 共享缓存时告诉 CDN / 反向代理按 Origin 区分缓存。:::八、安全考虑风险防护Origin 反射漏洞不要直接Access-Control-Allow-Origin: req.headers.origin用白名单校验携带 Cookie 跨站攻击Allow-Credentials: true 严格 Origin 白名单METHOD 反射不用Allow-Methods: req.headers[access-control-request-method]协议降级生产强制 HTTPS防 Origin 伪造::: dangerOrigin 反射是 CORS 最严重的安全漏洞// ❌ 致命错误任何网站都能跨域调你的 API res.setHeader(Access-Control-Allow-Origin, req.headers.origin); // ✅ 正确白名单校验 const origin req.headers.origin; if (WHITELIST.includes(origin)) { res.setHeader(Access-Control-Allow-Origin, origin); }:::九、最佳实践场景推荐开发环境用cors中间件一键配置详见 cors 中间件生产环境动态 Origin 白名单 Vary: Origin携带 CookieAllow-Credentials: true 具体 Origin预检缓存Max-Age: 8640024 小时安全永远不要反射 Origin / Methods十、小结CORS 通过响应头声明跨域权限浏览器自动处理预检和简单请求简单请求直接发服务器加Access-Control-Allow-Origin即可预检请求浏览器先发 OPTIONS 探测服务器必须响应携带 Cookie 必须三件套Allow-Credentials: true 具体 Origin 客户端credentials: include永远不要反射 Origin必须用白名单校验多个 Origin 时加Vary: Origin防止 CDN 缓存串
上一篇/下一篇内容由系统自动关联
返回资讯列表 →