一、CORS 核心概念
1. 同源策略
同源指的是 协议、域名、端口 三者完全一致。例如:
- 前端地址:
http://localhost:3000 - 后端地址:
http://localhost:8080
由于端口不同,属于不同源,前端请求后端接口时会被浏览器拦截。
2. 跨域请求分类
-
简单请求:满足以下条件的请求为简单请求
- 请求方法为
GET/HEAD/POST - 请求头仅包含
Accept/Accept-Language/Content-Language/Content-Type(值仅限application/x-www-form-urlencoded/multipart/form-data/text/plain)
简单请求会直接发送,浏览器在响应头中检查Access-Control-Allow-Origin字段判断是否允许跨域。
- 请求方法为
-
复杂请求 :不满足简单请求条件的请求(如
PUT/DELETE方法、自定义请求头)复杂请求会先发送 OPTIONS 预检请求,询问后端是否允许该跨域请求,预检通过后才会发送真实请求。
二、安装 cors 中间件
cors 是 Express 的官方推荐跨域处理中间件,通过 npm 安装:
bash
npm install cors
三、基础使用方案
1. 允许所有来源跨域(开发环境常用)
这是最简便的配置,直接启用 cors 中间件,允许所有域名访问后端接口:
javascript
var express = require('express')
var cors = require('cors')
var app = express()
// 全局启用 CORS,允许所有 origin
app.use(cors())
app.get('/api/data', (req, res) => {
res.json({ message: 'This is CORS-enabled for all origins!' })
})
app.listen(8080, () => {
console.log('Server running on port 8080')
})
适用场景:本地开发环境,无需限制访问来源。
2. 仅允许单个路由跨域
如果不需要全局跨域,可针对特定路由单独启用 cors:
javascript
var express = require('express')
var cors = require('cors')
var app = express()
// 仅 /api/products 路由允许跨域
app.get('/api/products/:id', cors(), (req, res) => {
res.json({ message: 'This is CORS-enabled for a Single Route' })
})
// 其他路由不允许跨域
app.get('/api/user', (req, res) => {
res.json({ message: 'No CORS for this route' })
})
app.listen(8080)
四、高级配置方案
1. 限制指定来源跨域(生产环境常用)
生产环境中,需要严格限制允许跨域的前端域名,避免恶意请求:
javascript
var express = require('express')
var cors = require('cors')
var app = express()
// 配置跨域选项
var corsOptions = {
// 仅允许 http://example.com 访问
origin: 'http://example.com',
// 兼容 IE11 等老旧浏览器(避免 204 状态码报错)
optionsSuccessStatus: 200
}
// 全局启用带配置的 CORS
app.use(cors(corsOptions))
app.get('/api/data', (req, res) => {
res.json({ message: 'Only example.com can access this!' })
})
app.listen(8080)
2. 动态白名单跨域
如果需要允许多个域名访问,可配置白名单,通过函数动态判断请求来源是否合法:
javascript
var express = require('express')
var cors = require('cors')
var app = express()
// 跨域白名单
var whitelist = ['http://example1.com', 'http://example2.com', 'http://localhost:3000']
var corsOptions = {
origin: function (origin, callback) {
// 白名单内的 origin 或无 origin(如 Postman 等工具请求)允许跨域
if (whitelist.indexOf(origin) !== -1 || !origin) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
}
}
app.use(cors(corsOptions))
app.get('/api/data', (req, res) => {
res.json({ message: 'Only whitelisted domains can access!' })
})
app.listen(8080)
关键说明 :!origin 用于兼容 Postman、curl 等无浏览器环境的工具请求,避免这些工具被误判为跨域。
3. 处理复杂请求的预检(OPTIONS)
对于 PUT/DELETE 等复杂请求,需要手动配置 OPTIONS 预检请求的处理:
javascript
var express = require('express')
var cors = require('cors')
var app = express()
// 全局处理所有路由的 OPTIONS 预检请求
app.options('*', cors())
// DELETE 方法属于复杂请求,需预检
app.delete('/api/data/:id', cors(), (req, res) => {
res.json({ message: 'DELETE request is CORS-enabled!' })
})
app.listen(8080)
核心原理 :app.options('*', cors()) 会拦截所有 OPTIONS 预检请求,返回跨域允许的响应头,确保后续真实请求能正常发送。
4. 异步配置跨域
如果需要从数据库或配置文件中动态获取白名单,可使用异步配置:
javascript
var express = require('express')
var cors = require('cors')
var app = express()
// 模拟从数据库获取白名单
function getWhitelistFromDB() {
return Promise.resolve(['http://example1.com', 'http://example2.com'])
}
// 异步跨域配置
var corsOptionsDelegate = async function (req, callback) {
var whitelist = await getWhitelistFromDB()
var corsOptions = { origin: false }
if (whitelist.indexOf(req.header('Origin')) !== -1) {
corsOptions.origin = true
}
callback(null, corsOptions)
}
app.get('/api/data', cors(corsOptionsDelegate), (req, res) => {
res.json({ message: 'Async CORS configuration!' })
})
app.listen(8080)
五、完整配置选项说明
cors 中间件支持丰富的配置项,覆盖所有 CORS 响应头需求:
| 配置项 | 类型 | 作用 | 示例 |
|---|---|---|---|
origin |
String/RegExp/Array/Function | 配置允许的跨域来源 | origin: 'http://example.com' |
methods |
String/Array | 配置允许的请求方法 | methods: ['GET', 'POST', 'PUT'] |
allowedHeaders |
String/Array | 配置允许的请求头 | allowedHeaders: ['Content-Type', 'Authorization'] |
exposedHeaders |
String/Array | 配置前端可访问的响应头 | exposedHeaders: ['X-Total-Count'] |
credentials |
Boolean | 是否允许携带 Cookie | credentials: true |
maxAge |
Number | 预检请求的缓存时间(秒) | maxAge: 86400(缓存 24 小时) |
preflightContinue |
Boolean | 是否将预检请求传递给下一个中间件 | preflightContinue: false |
optionsSuccessStatus |
Number | 预检请求成功的状态码 | optionsSuccessStatus: 200 |
关键配置:允许携带 Cookie
如果前端需要在跨域请求中携带 Cookie(如身份认证),需同时配置前后端:
-
后端配置
javascriptvar corsOptions = { origin: 'http://example.com', credentials: true // 允许跨域携带 Cookie } app.use(cors(corsOptions)) -
前端配置(Axios 示例)
javascriptaxios.get('http://localhost:8080/api/data', { withCredentials: true // 携带 Cookie })
六、常见问题与解决方案
-
问题 :配置后仍然跨域报错
原因 :可能是复杂请求未处理 OPTIONS 预检,或origin配置错误。
解决方案 :添加app.options('*', cors()),检查白名单是否包含前端真实域名。 -
问题 :IE11 浏览器跨域请求失败
原因 :IE11 不兼容 204 状态码。
解决方案 :配置optionsSuccessStatus: 200。 -
问题 :携带 Cookie 时跨域失败
原因 :credentials未开启,或origin配置为*(不能同时使用*和credentials: true)。
解决方案 :origin配置为具体域名,同时开启credentials: true。
七、总结
cors 中间件是 Express 处理跨域的高效工具,核心配置思路为:
- 开发环境 :使用
app.use(cors())允许所有来源。 - 生产环境:配置白名单限制来源,处理复杂请求的预检,必要时开启 Cookie 支持。
合理配置 CORS 既能保证前后端通信正常,又能提升接口的安全性