跨域是指一个域下的文档或脚本试图去请求另一个域下的资源。比如:
- 资源跳转: a链接、重定向、表单提交
- 资源嵌入: <link>、<script>、<img>、<frame>等dom标签,还有样式中background:url()、@font-face()等文件外链
- 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
我们通常所说的跨域主要是由于浏览器的 同源策略/SOP(Same origin policy) 限制的一类场景。
1. 同源策略(Same origin policy)
1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
所谓同源是指三个相同:协议相同、域名相同、端口相同,有一个及以上不同即为非同源。而非同源会限制以下三种行为:
1. Cookie、LocalStorage 和 IndexDB 无法读取。
2. DOM 无法获得。
3. AJAX 请求不能发送。
2. 解决方案
-
JSONP
在html页面中通过相应的标签从不同域名下加载静态资源,是被浏览器允许的,JSONP便是基于此原理。通过动态创建<script>元素,再请求一个带参网址实现跨域通信,服务器收到请求后,会将数据放在回调函数的参数位置返回。
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://xxxx.com:7001/json?callback=xxx'); //callback指定回调函数名字
}
//回调执行函数
function xxx(value) {
console.log(value)
}
缺点:只能发送get请求。
-
CORS / 跨源资源分享(Cross-Origin Resource Sharing)
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText).msg)
}
}
// xhr.withCredentials = true //设置是否带cookie
// 要发送Cookie,Access-Control-Allow-Origin就不能设为*,必须指定明确的、与请求网页一致的域名
xhr.open('GET', 'http://xxxx.com:7001/cros')
xhr.send(null)
cors很大程度上是服务端进行设置,返回响应,Response Headers会多出几个以Access-Control-开头的头信息字段。具体可参考
-
iframe + location.hash
a.html(父页面)
var iframe = document.createElement('iframe')
iframe.src = 'http://xxxx.com/hash.html'
document.body.appendChild(iframe);
window.onhashchange = function () {
console.log(location.hash) //通过监听hashchange事件得到通知
}
hash.html
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var res = JSON.parse(xhr.responseText)
parent.location.href = `http://yyyy.com/a.html#msg=${res.msg}` //改变父窗口的hash
}
}
xhr.open('GET', 'http://xxxx.com/json', true) //获取数据
xhr.send(null)
-
iframe + window.name
浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
a.html(父页面)
var iframe = document.createElement('iframe')
iframe.src = 'http://xxxx.com/name.html'
document.body.appendChild(iframe)
var times = 0
iframe.onload = function () {
if (++times === 2) {
console.log(JSON.parse(iframe.contentWindow.name)) //读取子窗口的window.name
//需要的话可以在获取数据后销毁这个iframe,释放内存;保证一定的安全性
//function destoryFrame() {
//iframe.contentWindow.document.write('');
//iframe.contentWindow.close();
//document.body.removeChild(iframe);
//}
}
}
name.html
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
window.name = xhr.responseText //将数据赋给window.name
location.href = 'http://yyyy.com/index.html' //跳至与父页面相同域名下
}
}
xhr.open('GET', 'http://xxxx.com/json', true) //获取数据
xhr.send(null)
window.name可以放置非常长的字符串(2MB),但是需要监听子页面window.name变化,影响页面性能。
-
iframe + postMessage
a.html(父页面)
var iframe = document.createElement('iframe')
iframe.src = 'http://xxxx.com/post.html'
document.body.appendChild(iframe)
window.addEventListener('message', function(e) { //监听消息,接受子页面数组
console.log(JSON.parse(e.data))
//可将数据处理后再传给子页面
//var data = e.data;
//data.name = 'a';
//iframe.contentWindow.postMessage(JSON.stringify(data), 'http://xxxx.com');
}, false);
post.html
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
parent.postMessage(xhr.responseText, '*') //子窗口向父窗口发信息
//postMessage(具体的信息内容, origin)
//origin可以是‘协议 + 域名 + 端口’;也可以是*,表示可以传递给任意窗口;或者‘/’表示和当前窗口同源
}
}
xhr.open('GET', 'http://xxxx.com/json', true) //获取数据
xhr.send(null)
-
WebSocket
WebSocket是HTML5一种新的协议,不实行同源策略。
可以用WebSocket+SockJS+Stomp或者WebSocket+SockJS -
nginx反向代理
服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略。
实现:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做中转,反向代理访问domain2接口,还可以顺便修改cookie中domain信息。
此方法可以不用目标服务器配合,是成本比较低的一种方法。