网上关于这类解决方案的描述有很多,都是差不多的内容,这里做一个摘要。
同源策略
abc.com
一级域名(主域名)
*.abc.com
二级域名(子域名)
1 主域相同时的跨域:document.domain
+ iframe
注:document.domain
只能设置为当前域的超级域(只能往上层设置,域名会变得更短)。
1)将两个域的document.domain
都设置为相同的主域,注意端口号。
2)还需利用iframe:创建iframe,将其src
设置为目标资源的地址,就可以通过获取ifr.contentDocument || ifr.contentWindow.document
来加载获取目标资源,然后再对数据进行所需操作。
2 window.name
+ iframe
该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的。window.name属性的神奇之处在于name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。
能使用这种方式跨域,有几个条件必不可少:
- iframe标签的跨域能力
- window.name属性值在文档刷新后依旧存在的能力
代码示例:
涉及3个页面:资源请求页a.com/cs1.html
),资源请求代理页(a.com/proxy.html
),资源提供页(b.com/cs1.htm
)。(前两个同源,最后一个不同源。)
- 将目标域(资源提供页)内的资源内容(要传送的内容)赋值给资源提供页的
window.name
。
|
|
资源请求代理页中
proxy
函数的功能:资源请求代理页添加iframe,
src
指向目标域的资源提供页;第一次加载时,
ifr.contentWindow.location
指向资源请求页;之后页面刷新,就得到
ifr.contentWindow.name
, 用回调函数func
处理数据。
代码:
|
|
补充:iframe.src
与 ifr.contentWindow.location.href
的区别:
每个iframe都有个子窗口(可以用iframe.contentWindow获取)
重置iframe.src
或者 iframe.contentWindow.location.href
都会引起子窗口页面的刷新,以及内容的变化(指向重置后的url)
如果重置的是iframe.src
,那么刷新后iframe.src === iframe.contentWindow.location.href
;
如果重置的是iframe.contentWindow.location.href
,iframe.src
值不会变化。
3 location.hash
+ iframe
原理是利用location.hash来进行传值。
假设域名a.com下的文件cs1.html要和b.com域名下的cs2.html传递信息。
1) cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向b.com域名下的cs2.html页面
2) cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据
3) 同时在cs1.html上加一个定时器,隔一段时间来判断location.hash
的值有没有变化,一旦有变化则获取获取hash值
注:由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash
的值,所以要借助于a.com域名下的一个代理iframe
代码如下:
先是a.com下的文件cs1.html文件:
|
|
b.com域名下的cs2.html:
|
|
a.com下的域名cs3.html
//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
优点:
- 可以解决域名完全不同的跨域。
- 可以实现双向通讯。
缺点:
location.hash
会直接暴露在URL里,并且在一些浏览器里会产生历史记录,数据安全性不高也影响用户体验。- 由于URL大小的限制,支持传递的数据量也不大。
- 有些浏览器不支持
onhashchange
事件,需要轮询来获知URL的变化。
4 window.postMessage
eg1: 父子窗口通信
父窗口:aaa.com 发消息
|
|
子窗口:bbb.com 接收消息
postMessage
方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即”协议 + 域名 + 端口”。也可以设为*,表示不限制域名,向所有窗口发送。
父窗口和子窗口都可以通过message事件,监听对方的消息。message事件的事件对象event,提供以下三个属性:
- event.source:发送消息的窗口
- event.origin: 消息发向的网址
- event.data:消息内容
|
|
eg2: 可以利用iframe:
1) a.com/index.html中的代码:
|
|
2) b.com/index.html中的代码:
|
|
5 JSONP(动态创建script标签)
基本原理:网页通过添加一个<script>
元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
JSONP包含两部分:回调函数和数据。
回调函数是当响应到来时要放在当前页面被调用的函数(handleResponse()
)。
数据就是传入回调函数中的json数据,也就是回调函数的参数了({"data": "zhe"}
)。
例子如下:
|
|
优点:简单适用,老式浏览器全部支持,服务器改造小。不需要XMLHttpRequest或ActiveX的支持。
缺点:只支持GET请求。
这里面试曾经被问到:JSONP有什么不好的地方?
1)安全问题(请求代码中可能存在安全隐患)
2)要确定JSONP请求是否失败并不容易
6 WebSockets协议
WebSockets协议不受同源策略限制,只要支持该协议的服务器就可以。
|
|
7 CORS
Cross-Rrigin Resource Sharing 跨域资源共享
对开发者来说,CORS请求与同源的AJAX请求无异,由浏览器自动完成CORS请求。
所有浏览器都支持该功能(通过 XMLHttpRequest 对象发起),除了IE浏览器不能低于IE10(Internet Explorer 8 和 9 可以通过 XDomainRequest 对象来实现CORS)。
关键是服务器需要实现CORS接口。
向跨域服务器发送XmlHttpRequest
请求,克服AJAX的同源策略。
注意区分简单请求和非简单请求的定义。两种请求的CORS实现流程不同:
- 对于简单请求:
- 浏览器在请求头中添加
origin
字段。服务端判断收到请求的origin
是否在白名单中。浏览器根据返回的响应头字段中的Access-Control-
字段判断是否跨域请求成功。 - cookies问题:
withCredentials
字段设置客户端(若设为true
,则服务器给的响应头中Access-Origin-Allow-Origin
必须指定允许请求的域名,不能使用“*”),Access-Control-Allow-Credentials
设置服务端。cookie仍受同源策略限制。
- 浏览器在请求头中添加
- 对于非简单请求:
- 浏览器先发送预检请求(OPTIONS方法,带上希望CORS请求的
Access-Control-Request-Method
,Access-Control-Request-Header
字段); - 得到肯定答复(有
Access-Control-Allow-Origin
等CORS相关字段)后, - 浏览器和服务器用简单请求通信(每次请求带上
origin
字段,每次响应也有Access-Control-Allow-Origin
字段)。 - 如果浏览器否定预检请求,会返回正常HTTP响应,但是没有任何CORS相关的头信息字段,同时触发错误,被
XmlHttpRequest
的对象的onerror
捕获,打印在控制台。
- 浏览器先发送预检请求(OPTIONS方法,带上希望CORS请求的
CORS优点:是跨域请求的根本解决办法,支持所有HTTP方法。