URL.prototype.origin是最可靠方式,能精确提取协议+主机+端口三元组用于跨域白名单校验;但不支持相对URL、不标准化host大小写,且对file://或data://等非网络协议会失效。

URL.prototype.origin 是目前最直接、零依赖、浏览器和 Node.js(v10.12+)原生支持的方式,能精确提取协议 + 主机 + 端口三元组用于跨域白名单校验——但必须清楚它不处理相对 URL、不标准化 host 大小写,且对 file:// 或 data:// 会返回空字符串或抛错。
为什么不用 string.split('://') 或正则硬切 origin?
手动解析极易出错:https://[::1]:3000(IPv6)、http://a.com/path:to:res(路径含冒号)、HTTPS://EXAMPLE.COM:443(大小写混用+默认端口)都会让正则崩掉。而 new URL() 构造器自动归一化这些情况,.origin 只返回标准格式:protocol://host:port(注意:80/443 端口被自动省略,是规范行为,不是 bug)。
Node.js 后端做 Origin 校验时的典型踩坑点
常见错误是直接拿 req.headers.origin 做字符串比对,却忽略:
-
Origin请求头可能为空(如 curl、Postman、file://页面发起的请求),new URL(origin)会抛TypeError - 用户可伪造
Origin头,所以必须配合白名单严格全等匹配,不能用includes或startsWith - 白名单项本身也应统一用
new URL(allowed).origin解析后存储,否则大小写差异(HTTPS://vshttps://)或端口写法(:8080vs 省略)会导致漏判
Express 中间件里安全使用 URL.prototype.origin 的写法
关键逻辑:先防御性判断空值 → 再解析 → 再白名单全等比对。示例:
app.use('/ws', (req, res, next) => {
const originHeader = req.headers.origin
if (!originHeader) return res.status(403).end()
let origin
try {
origin = new URL(originHeader).origin
} catch {
return res.status(403).end()
}
const allowedOrigins = [
'https://myapp.com',
'https://staging.myapp.com',
'http://localhost:3000'
]
if (!allowedOrigins.includes(origin)) {
return res.status(403).end()
}
next()
})
注意:这里 allowedOrigins 里的每一项都必须是已归一化的 .origin 格式(小写协议、无默认端口、无路径),否则比对失败。
Gorilla WebSocket 的 CheckOrigin 为何不能只靠 strings.EqualFold?
因为 Origin 头里可能带端口(http://localhost:3000),而 r.Host 可能是 localhost:8080;若仅靠 strings.EqualFold(origin, r.Host),会误判。正确做法是:
- 先用
new URL(origin).origin提取标准源 - 再与白名单中预解析好的
.origin值比对(不是原始字符串) - 避免把
https://example.com:443和https://example.com当作两个不同源(它们其实是同一个)
真正容易被忽略的是:白名单数组本身是否经过 new URL(x).origin 预处理——没做这步,整个校验就形同虚设。

















