阮一峰同源策略
浏览器处于安全考虑实行了同源策略机制。
同源指的是
- 协议相同
- 域名相同
- 端口相同
举例来说,http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略)。
域名分为 顶级域名,一级域名,二级域名等。顶级域名就是一级域名,比如 .com .org .cn,N级域名就是在N-1级域名前追加一级。
非同源的话限制如下
- Cookie、LocalStorage 和 IndexDB 无法读取。
- DOM 无法获得。
- AJAX 请求不能发送。
我们首先来看跨域如何请求
1. 跨域如何请求
1.1 JSONP
jsonp的实现是通过<script>标签,因为<script>标签是没有同源限制的。前端通过<script>标签发送请求带上例如callback=foo的参数。服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
// 前端
<script type="text/javascript" src="'http://example.com/ip?callback=foo"></script>
// 后端返回数据
foo({
"ip": "8.8.8.8"
});
具有src属性的如img,iframe,srcipt都不受同源策略的影响,这样代码就可以正常请求数据并执行回调函数了。
1.2 CORS
CORS简介
CORS需要前后端同时支持,目前,所有浏览器都支持该功能,浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
主要就是后端设置以下几个响应头
Access-Control-Allow-Origin: http://api.bob.com // 允许请求的来源
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Expose-Headers: FooBar
跨域携带cookie设置withCredentials is true时候不能设置 Access-Control-Allow-Credentials 为* 参考
在使用CROS的时候我们的请求分为简单请求和非简单请求
只要同时满足以下两大条件,就属于简单请求。
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
非简单请求会发预请求OPTIONS。预请求通过后才能发送真正的请求,预请求OPTINS失败的几个原因
如果'Content-Type': 'application/json'等非简单请求的内容跨域的时候回发options请求。并且
请求头会加上
Access-Control-Request-Headers:content-type。这时候options在控制台看是200,但是不会发送真正的请求。
所以服务器端还必须设置一个名为Access-Control-Allow-Headers
的Header
,将它的值设置为 Content-Type
,表明服务器能够接收到前端发送的请求中ContentType
属性并使用它的值。还有几种OPTIONS出错的场景参考链接
1.3 代理
1.4 WebSocket
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
2. cookie
document.domain 。
两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。
A网页是http://w1.example.com/a.html
,B网页是http://w2.example.com/b.html
,那么只要设置相同的document.domain= 'example.com';
,两个网页就可以共享Cookie。
这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法
3. iframe
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。
比如,父窗口运行下面的命令,如果iframe窗口不是同源,就会报错。
如果两个窗口一级域名相同,只是二级域名不同,那么设置上一节介绍的document.domain属性,就可以规避同源政策,拿到DOM。
对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。
片段识别符(fragment identifier)
window.name
跨文档通信API(Cross-document messaging)
3.1 片段识别符
就是Hash,URL的#号后面的部分,通过监听hashchange事件得到通知
3.2 window.name
浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
3.3 H5的window.postMessage
postMessage:跨文档通信
postMessage作用简介
举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以了。
var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');
postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。
子窗口向父窗口发送消息的写法类似。
window.opener.postMessage('Nice to see you', 'http://aaa.com');
父窗口和子窗口都可以通过message事件,监听对方的消息。
window.addEventListener('message', function(e) {
console.log(e.data);
},false);