一、跨域
1、同源策略
浏览器同源策略限制请求
同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
注:二级域名不同也算,因为判断源是否相同时,是根据URL判断的
限制以下行为
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 Js对象无法获得
- AJAX 请求不能发送
有三个标签是允许跨域加载资源:
- <img src=XXX>
- <link href=XXX>
- <script src=XXX>
2、解决方案
1)通过jsonp跨域
script标签不受策略影响,可以动态生成script去请求数据,但是仅限Get请求
原生实现
html复制代码<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参并指定回调执行函数为onBack
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';
document.head.appendChild(script);
// 回调执行函数
function onBack(res) {
alert(JSON.stringify(res));
}
</script>
vue实现
js复制代码this.$http.jsonp(
'http://www.domain2.com:8080/login',
{
params: {},
jsonp: 'onBack'
}
).then(res => { console.log(res); }
)
2)document.domain + iframe跨域
仅限主域相同,子域不同的跨域应用场景,将domain提到顶级域名,完成跨域
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域
MDN: 满足某些限制条件的情况下,页面是可以修改它的源。脚本可以将 document.domain 的值设置为其当前域或其当前域的父域。
html复制代码<!-- 父窗口:http://www.domain.com/a.html -->
<iframe id="iframe" src="http://child.domain.com/b.html">
</iframe>
<script>
document.domain = 'domain.com';
var user = 'admin';
</script>
<!-- 子窗口:http://child.domain.com/b.html -->
<script>
document.domain = 'domain.com';
// 获取父窗口中变量
alert('get js data from parent ---> '+ window.parent.user);
</script>
3)location.hash + iframe
实现原理: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
实际就是修改URL的 # 后面部分,我们可以通过监听hashchange事件完成
具体实现: A域:a.html -> B域:b.html -> A域:c.html,
a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。
4)window.name + iframe跨域
window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
5) postMessage跨域
postMessage一般用于解决以下问题
a.) 页面和其打开的新窗口的数据传递
b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递
d.) 上面三个场景的跨域数据传递
html复制代码<!-- a页面:http://www.domain1.com/a.html -->
<iframe id="iframe"
src="http://www.domain2.com/b.html"
style="display:none;">
</iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym'
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage
(JSON.stringify(data),
'http://www.domain2.com');
};
// 接受domain2返回数据
window.addEventListener
('message', function(e) {
alert('data from domain2 ---> ' + e.data);
},
false);
</script>
<!-- b页面:http://www.domain2.com/b.html -->
<script>
// 接收domain1的数据
window.addEventListener
('message', function(e) {
alert('data from domain1 ---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data),
'http://www.domain1.com');
}
}, false);
</script>
6)跨域资源共享(CORS):主流的跨域解决方案
服务端设置Access-Control-Allow-Origin即可
若要带cookie请求:前后端都需要设置。
前端: : 检查前端设置是否带cookie:xhr.withCredentials = true;
通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
简单请求:
使用下列方法之一:
- GET
- HEAD
- POST
Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
不符合以上条件的请求就肯定是复杂请求了。 复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求, 该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。
OPTIONS预检请求
请求头:
- Origin:当前请求源,和响应头里的Access-Control-Allow-Origin 对标, 是否允许当前源访问,Origin是不可修改的
- Access-Control-Request-Headers:本次真实请求的额外请求头,和响应头里的Access-Control-Allow-Headers对标,是否允许真实请求的请求头
- Access-Control-Request-Method:本次真实请求的额外方法,和响应头里的Access-Control-Allow-Methods对标,是否允许真实请求使用的请求方法
响应头
- Access-Control-Allow-Credentials:
- 这里的Credentials(凭证)其意包括:Cookie ,授权标头或 TLS 客户端证书,默认CORS请求是不带Cookies的,这与JSONP不同,JSONP每次请求都携带Cookies的,当然跨域允许带Cookies会导致CSRF漏洞。如果非要跨域传递Cookies,web端需要给ajax设置withCredentials为true,同时,服务器也必须使