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

直接结论:URL.prototype.origin 是最可靠、零依赖、浏览器和 Node.js(v10.12+)原生支持的方式,能精确提取协议+主机+端口三元组,用于跨域白名单校验——但必须注意它不处理相对 URL、不标准化 host 大小写、且无法覆盖 file:// 或 data:// 等非网络协议场景。
为什么不能用 string.split('://') 或正则硬切?
手动解析 URL 字符串极易出错:端口可能被省略(https://example.com)、host 可能含 IPv6(https://[::1]:3000)、路径中也可能出现冒号(http://a.com/path:to:res)。而 URL 构造器会自动归一化这些情况,origin 属性只返回标准格式:protocol://host:port。例如:
new URL('HTTPS://EXAMPLE.COM:443/any/path?x=1#hash').origin // 'https://example.com'
注意:端口 80/443 会被自动省略,这是规范行为,不是 bug。
在 Node.js 后端做请求来源校验时的典型误用
常见错误是直接拿 req.headers.origin 做字符串比对,却忽略:
-
Origin请求头可能为空(如某些 curl 或 Postman 调用不带该头),new URL()会抛TypeError - 用户可伪造
Origin头,所以必须配合白名单做严格匹配,不能用includes或startsWith - 白名单项本身也应统一用
URL解析后取.origin存储,避免大小写或端口写法不一致导致漏判
正确做法示例(Express 中间件):
const ALLOWED_ORIGINS = ['https://app.example.com', 'https://staging.app.example.com'].map(s => new URL(s).origin);<br><br>app.use((req, res, next) => {<br> const originHeader = req.headers.origin;<br> if (!originHeader) return res.status(403).end();<br> try {<br> const origin = new URL(originHeader).origin;<br> if (ALLOWED_ORIGINS.includes(origin)) {<br> res.setHeader('Access-Control-Allow-Origin', origin);<br> return next();<br> }<br> } catch (e) { /* 忽略非法 origin */ }<br> res.status(403).end();<br>});
前端发请求前用 origin 校验目标是否在可信域内
这通常用于代理层或 SDK 内部防护(比如防止误调用生产 API):
- 不能仅靠
window.location.origin判断当前页,因为 iframe 或 sandbox 环境下可能不同 - 对目标 URL 调用
new URL(targetUrl).origin后,再与预设白名单比对;若目标是相对路径(如/api/users),需先 resolve 成绝对 URL - 特别注意:
blob:、chrome-extension:等非 HTTP(S) 协议的origin返回值是空字符串或特殊格式(如"null"),这类必须显式拒绝
示例:
function isTrustedTarget(urlString) {<br> try {<br> const url = new URL(urlString, document.baseURI);<br> const origin = url.origin;<br> if (!origin || origin === 'null') return false;<br> return ['https://api.prod.com', 'https://api.dev.com'].includes(origin);<br> } catch (e) {<br> return false;<br> }<br>}
真正容易被忽略的是:白名单列表里的每个域名,都得走一遍 new URL(...).origin 初始化,否则像 https://API.PROD.COM 和 https://api.prod.com 就会因 host 大小写不一致而匹配失败——origin 属性返回的 host 永远是小写,但你自己手写的白名单字符串未必是。











