什么是跨域?
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。
广义的跨域:
1.) 资源跳转: A链接、重定向、表单提交
2.) 资源嵌入:<link>、<script>、<img>、<frame>等dom标签,还有样式中background:url()、@font-face()等文件外链
3.) 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
其实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。
什么是同源策略?
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。
所谓同源指的是:协议,域名,端口相同,同源策略是一种安全协议,指一段脚本只能读取来自同一来源的窗口和文档的属性。
同源策略限制以下几种行为:
1.) Cookie、LocalStorage 和 IndexDB 无法读取
2.) DOM 和 Js对象无法获得
3.) AJAX 请求不能发送
第一,如果是协议和端口造成的跨域问题“前台”是无能为力的, 第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。
同源策源如下:
同一域名下 允许
http://www.a.com/a.js
http://www.a.com/b.js
同一域名下不同文件夹 允许
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名,不同端口 不允许
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同协议 不允许
http://www.a.com/a.js
https://www.a.com/b.js
域名和域名对应ip 不允许
http://www.a.com/a.js
http://70.32.92.74/b.js
主域相同,子域不同 不允许
http://www.a.com/a.js
http://script.a.com/b.js
同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)
http://www.a.com/a.js
http://a.com/b.js
不同域名 不允许
http://www.cnblogs.com/a.js
http://www.a.com/b.js
解决方案: 1、 通过jsonp跨域
2、 document.domain + iframe跨域
3、 location.hash + iframe
4、 window.name + iframe跨域
5、 postMessage跨域
6、 跨域资源共享(CORS)
7、 nginx代理跨域
8、 nodejs中间件代理跨域
9、 WebSocket协议跨域
跨域解决方案有哪些?
jsonp
jsonp并不是一种数据格式,而json是一种数据格式,jsonp是用来解决跨域获取数据的一种解决方案,具体是通过动态创建script标签,然后通过标签的src属性获取js文件中的js脚本,该脚本的内容是一个函数调用,参数就是服务器返回的数据,为了处理这些返回的数据,需要事先在页面定义好回调函数,本质上使用的并不是ajax技术。
所有的src属性和href属性都不受同源策源的限制,可以请求第三方服务器的内容。但只能解决get跨域问题。
一个简单的jsonp封装
原理:动态的创建一个script标签。
//对请求data进行格式化处理
function formateData(data) {
let arr = [];
for (let key in data) {
//避免有&,=,?字符,对这些字符进行序列化
arr.push(encodeURIComponent(key) + '=' + data[key])
}
return arr.join('&');
}
//跨域jsonp请求
function jsonp(params) {
//先对params进行处理,防止为空
params = params || {};
params.data = params.data || {};
//后台传递数据时调用的函数名
var callbackName = params.jsonp;
// 拿到dom元素head,先不进行操作
var head = document.querySelector('head');
//创建script元素,先不进行操作
var script = document.createElement('script');
//传递给后台的data数据中,需要包含回调参数callback。
//callback的值是 一个回调函数的函数名,后台通过该回调函数名调用传递数据,这个参数名的key由双方约定,默认为callback
params.data['callback'] = callbackName;
//对data数据进行格式化
var data = formateData(params.data);
//设置script请求的url跟数据
script.src = `${params.url}?${data}`;
//script.src = "http://127.0.0.1:8080/index.html?callback=jsonpCallback"
//全局函数 由script请求后台,被调用的函数,只有后台成功响应才会调用该函数
window[callbackName] = function (jsonData) {
//请求移除scipt标签
head.removeChild(script);
clearTimeout(script.timer);
window[callbackName] = null;
// jsonp返回的数据是json对象,可以直接使用
// ajax返回的是json字符串,需要转换才能使用
params.success && params.success(jsonData)
}
//请求超时的处理函数
if (params.time) {
script.timer = setTimeout(() => {
//请求超时对window下的[callbackName]函数进行清除,由于有可能下次callbackName发生改变了
window[callbackName] = null;
//移除script元素,无论请求成不成功
head.removeChild(script)
//这里不需要清除定时器了,clearTimeout(script.timer); 因为定时器调用之后就被清除了
//调用失败回调
params.error && params.error({
message: '超时'
})
}, time);
}
//往head元素插入script元素,这个时候,script就插入文档中了,请求并加载src
head.appendChild(script);
}
jsonp({
url: 'http://127.0.0.1:3000/jsonp',
jsonp: 'callback',
data: {
name: 'jgchen',
stuNo: 2016130201,
method: 'jsonp'
},
success(res) {
console.log('jsonp success:',res);
},
error(err) {
console.log(err);
}
})
无论是请求超时,还是请求成功,都要移除script元素,script元素只有在第一次插入页面文档的时候,才会请求src
无论请求失败还是成功,都还是要移除window[callbackName]避免增加没用的全局方法,因为每次请求的callbackName可能是不同的。
jsonp跨域步骤
可以看出jsonp解决跨域的步骤:
- 创建一个
script标签 - 给
script标签的 src 属性设置接口地址,并带一个回调函数名称 - 将
script标签插入 head 头部 - 定义回调函数接收后台返回的数据
为什么jsonp只能是get请求?
因为script的加载就是get方式的~
CORS跨域资源共享
原理:服务器设置Access-Control-Allow-OriginHTTP响应头之后,浏览器会允许跨域请求。
// 需要后台设置
Access-Control-Allow-Origin:* // 允许所有域名访问
Access-Control-Allow-Origin:"http://a.com" // 允许特定域名访问
document.domain
原理:相同主域名不同子域名下的页面,可以设置document.domain让它们同域。
同域 document 提供的是页面间的相互操作,需要载入 iframe 页面。
var ifr = document.createElement("iframe")
ifr.src = "http://b.a.com/bar"
ifr.onload = function(){
var ifrdoc = ifr.contentDocument || ifr.contentWindow.document
ifrdoc.getElementById("foo").innerHTML
}
ifr.style.display = 'none'
document.body.appendChild(ifr)
ES5 postMessage
ES5新增的 postMessage() 方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档,多窗口,跨域消息传递。
postMessage(data,origin)