跨域知识点整理

194 阅读5分钟

什么是跨域?

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。

广义的跨域:

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)
 

代理