JS-彻底告别跨域烦恼:从同源策略到 CORS 深度实战
前言
在 Web 开发中,“跨域”是每个前端开发者绕不开的坎。当你看到控制台报出 Access-Control-Allow-Origin 错误时,其实是浏览器的同源策略在起作用。本文将带你深度解析跨域的本质,并掌握主流的解决方案。
一、 什么是跨域?
1. 同源策略 (Same-origin policy)
跨域问题的根源是浏览器为了安全而实施的同源策略。所谓“同源”,是指两个 URL 的以下三部分完全相同:
- 协议:http 、 https
-
域名 :域名就是我们每次访问网站输入的网址,每个域名都对应了一个IP地址,浏览器会通过域名解析来获取这个IP地址,例如
www.test.com - 端口号 :80 、 8080
2. 域名解析小科普
域名是 IP 地址的“外壳”。
- 顶级域名:.com, .cn
- 一级域名:test.com
-
二级域名:
www.test.com -
注意:一级域名和二级域名之间、二级域名和三级域名之间,统统属于跨域!比如在
www.test.com网页使用 XMLHttpRequest 请求time.test.con的页面内容,由于它们不是同一个源,所以就涉及到了跨域(在 A 站点中去访问不同源的 B 站点的内容)。默认情况下,跨域请求是不被允许的,你可以看下面的示例代码:
二、 解决方案一:JSONP
1. 实现原理
利用 <script> 标签的 src 属性不受同源策略限制的特性。通过动态创建 script 标签,发送一个带有 callback 参数的 GET 请求。
2. 代码实现
前端逻辑:
btn.click(() => {
var script = document.createElement("script");// 创建 scrip 标签
script.src = `http://localhost:3000?callback=show`;// 添加 src 请求路径
document.body.appendChild(script);
script.onload = function(){
document.body.removeChild(script)
}
});
//这个函数就是回调函数,它会拼接到src属性中,并对数据进行操作
function show(result) {
// ...
console.log("获取到的数据:", result);
}
服务端逻辑:
const http = require("http")
const url = require("url")
http.createServer(
(req,res)=>{
var callback = url.parse(req.url,true).query.callback;
var severData = "xxxxxxxx";
severData = JSON.stringify(severData)
res.writeHead(200,{
"Content-Type": "text/plain;charset=utf-8"
});
res.write(`${callback}(${severData})`);
res.end();
}
).listen(80)
局限性:仅支持 GET 请求,不安全,且无法处理复杂的报错信息。
三、 解决方案二:CORS (现代的标准方案)
CORS(跨域资源共享)是目前的标准解法。它将请求分为简单请求和非简单请求。
1. 简单请求
条件:方法为 GET/POST。
-
流程:浏览器直接发起请求,并在 Header 中带上
Origin。 -
服务端:通过返回
Access-Control-Allow-Origin来告知浏览器是否放行。
2. 非简单请求(预检请求)
条件:包含 PUT/DELETE 方法。
-
流程:浏览器会先发送一个 OPTIONS 方法的“预检请求”。
-
关键字段:
-
Access-Control-Max-Age: 设置预检请求的缓存时间(秒),避免每次请求都多发一次 OPTIONS,优化性能。
-
3. Nginx 服务端配置示例
server {
listen 80;
location / {
# 允许跨域的域名,建议生产环境指定具体域名而非 *
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
# 处理预检请求
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
return 204;
}
}
}
四、 面试模拟题
Q1:为什么要有同源策略?如果没有会怎样?
参考回答:
同源策略主要是为了防止 CSRF(跨站请求伪造) 攻击。如果没有同源策略,黑客的网页可以随意读取你银行网页的 Cookie 或 DOM 内容,从而冒充你发送请求或窃取敏感信息。
Q2:CORS 预检请求(OPTIONS)在什么情况下会触发?
参考回答:
当请求满足以下任意条件时会触发预检:
- 使用了
PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH方法。 - 设置了非简单的 Header 字段(如
Authorization、自定义 Token)。 -
Content-Type的值不属于application/x-www-form-urlencoded、multipart/form-data、text/plain。
Q3:如何解决跨域时 Cookie 无法携带的问题?
参考回答:
-
前端:
XMLHttpRequest或fetch需设置withCredentials: true。 -
服务端:设置响应头
Access-Control-Allow-Credentials: true。 -
注意:当开启凭证携带时,
Access-Control-Allow-Origin不能设置为*,必须指定具体的域名。
五、 总结
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| JSONP |
<script> 标签不受限 |
兼容性极好(老浏览器) | 只支持 GET,安全性差 |
| CORS | 服务端 Header 授权 | 正式标准,支持所有方法 | 需服务端配合,有预检开销 |