跨域以及解决方案汇总

跨域

是指一个域下面的文档或者脚本视图去请求另一个域下的资源

  1. 资源跳转:A链接,重定向,表单提交
  2. 资源嵌入:<link> , <script> , <img> , <frame>dom 标签, 还有样式中的background: url(), @font-face() 等文件外链
  3. 脚本请求: js 发起的 ajax 请求, domjs 对象的跨域请求

同源策略

由NetScape公司1995年引入浏览器,是浏览器最核心也最基本的安全功能。如果没有这个功能,浏览器很容易受到XSS,CSFR等攻击。同源即 协议 + 域名 + 端口 三者相同,即使两个域名指向同一个ip地址,也不是同源。

同源策略限制一下几种行为

  1. CookieLocalStorageIndexDB 无法读取
  2. DOMJS 对象无法获得
  3. AJAX 请求不能发送

跨域解决方案

  1. 通过 JSONP 跨域

    JSONP 只能get请求

     <script src="http://www.b.com/request?callback=jsonPCallBack">
         function jsonPCallBack(content) {...}
        </script>
    
    $.ajax("http://www.b.com/request", {
      jsonpCallback: "moty",
      dataType: "jsonp",
      success: function(json) {...}
    });
    
  2. document.domain + iframe 跨域

    a. 这两个域名必须属于同一个一级域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域。

    b. 如果是同一级域名下的子域名,如:m.a.com, api.a.com, 需要在 javascript 里面设置 domain 才行

    document.domain = 'a.com';
    

    news.baidu.com下的news.html页面:

    <script>
        document.domain = 'baidu.com';
        var ifr = document.createElement('iframe');
        ifr.src = 'map.baidu.com/map.html';
        ifr.style.display = 'none';
        document.body.appendChild(ifr);
        ifr.onload = function(){
            var doc = ifr.contentDocument || ifr.contentWindow.document;
            // 这里可以操作map.baidu.com下的map.html页面
            var oUl = doc.getElementById('ul1');
            alert(oUl.innerHTML);
            ifr.onload = null;
        };
    </script>
    

    map.baidu.com下的map.html页面:

    <ul id="ul1">我是map.baidu.com中的ul</ul>
    <script>
     document.domain = 'baidu.com';
    </script>
    
  3. location.hash + iframe

    原理是利用location.hash来进行传值。

    在url: http://a.com#helloword中的‘#helloworld’就是location.hash,改变hash并不会导致页面刷新,所以可以利用hash值来进行数据传递,当然数据容量是有限的。

    假设域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html传递信息,cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html页面,这时的hash值可以做参数传递用。

    cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据(由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe;Firefox可以修改)。

    同时在cs1.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一点有变化则获取获取hash值。

    a.html下的文件cs1.html

    function startRequest(){
        var ifr = document.createElement('iframe');
        ifr.style.display = 'none';
        ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo';
        document.body.appendChild(ifr);
    }
     
    function checkHash() {
        try {
            var data = location.hash ? location.hash.substring(1) : '';
            if (console.log) {
                console.log('Now the data is '+data);
            }
        } catch(e) {};
    }
    setInterval(checkHash, 2000);
    

    cnblogs.com下的cs2.html

    //模拟一个简单的参数处理操作
    switch(location.hash){
        case '#paramdo':
            callBack();
            break;
        case '#paramset':
            //do something……
            break;
    }
     
    function callBack(){
        try {
            parent.location.hash = 'somedata';
        } catch (e) {
            // ie、chrome的安全机制无法修改parent.location.hash,
            // 所以要利用一个中间的cnblogs域下的代理iframe
            var ifrproxy = document.createElement('iframe');
            ifrproxy.style.display = 'none';
             // 注意该文件在"a.com"域下
            ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata';   
            document.body.appendChild(ifrproxy);
        }
    }
    

    a.html下的文件cs3.html

    //因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
    parent.parent.location.hash = self.location.hash.substring(1);
    
  4. window.name + iframe

    window.name (一般在js代码里出现)的值不是一个普通的全局变量,而是当前窗口的名字,这里要注意的是每个iframe都有包裹它的window,而这个window是top window的子窗口,而它自然也有window.name的属性, window.name属性的神奇之处在于name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。

    window.name = 'abc';
    window.name; // abc
    window.location = 'http://www.baidu.com';
    window.name; // abc
    

    a.html, proxy.html 同属于一个域下,b.html是另一个域的

    b.html内容

    <script>
     window.name = 'b.html\'s data';
    </script>
    

    a.html

    <script type="text/javascript">
    var otherLoaded = false,
    iframe = document.createElement('iframe'),
    loadfn = function() {
        if (otherLoaded) {
            var data = iframe.contentWindow.name; // 读取数据
            alert(data); // 弹出b.html's data
         // 清理工作
            iframe.contentWindow.document.write('');
            iframe.contentWindow.close();
            document.body.removeChild(iframe);
        } else if (!otherLoaded) {
            otherLoaded = true;
            // 设置的代理文件
            iframe.contentWindow.location = "http://localhost:8001/proxy.html"; 
        }
    };
    iframe.src = 'http://localhost:8002/b.html';
    if (iframe.attachEvent) {
        iframe.attachEvent('onload', loadfn);
    } else {
        iframe.onload = loadfn;
    }
    document.body.appendChild(iframe);
    </script>
    

    可以看到, 第一次设置iframe的地址为b.html, 这样的话b.html会被加载进来,但是并不能直接访问iframe.contentWindow.name, 因为a.html和b.html目前不同源,如果将loadfn的实现改为var data = iframe.contentWindow.name;,会出来这个错误:

    a.html: 
    Uncaught DOMException: Blocked a frame with origin "http://localhost:8001" from accessing a cross-origin frame.
    

    那怎么办呢, 既然不同源, 就改成同源呗, 所以将iframe地址改成与a.html同源的proxy.html,由于window.name在地址变化时值不变, 所以iframe.contentWindow.name的值还是之前的值, 也就是b.html窗口的值, 而又满足的同源的要求, 所以可以访问成功。

  5. postMessage

    window.postMessage的功能是允许程序员跨域在两个窗口/frames间发送数据信息。基本上,它就像是跨域的AJAX,但不是浏览器跟服务器之间交互,而是在两个客户端之间通信。安全着想,接受消息的时候应该检验来源,也就是event.origin或者event.source。

    a.html

    <h1 class="header">page A</h1>
    <div class="mb20">
        <textarea name="ta" id="data" cols="30" rows="5">hello world</textarea>
        <button style="font-size:20px;" onclick="send()">post message</button>
    </div>
    <iframe src="http://localhost:9022/b.html" id="child" 
    

style="display: block; border: 1px dashed #ccc; height: 300px;"></iframe>

<script>
function send() {
var data = document.querySelector('#data').value;
// 触发跨域子页面的messag事件
window.frames[0].postMessage(data, 'http://localhost:9022/');
}

   window.addEventListener('message', function(messageEvent) {
       if (messageEvent.origin !== 'http://localhost:9022/') return
    var data = messageEvent.data; 
    console.info('message from child:', data);
}, false);

</script>


b.html

```html
<h1 class="header">page B</h1>

<input type="text" id="inp" value="some contents..">
<button onclick="send()">send</button>
<script>
window.addEventListener('message', function(ev) {
    if (ev.source !== window.parent) {return;}
    var data = ev.data;
    console.info('message from parent:', data);
}, false);

function send() {
    var data = document.querySelector('#inp').value;
    // 若父页面的域名和指定的不一致,则postMessage失败
    window.parent.postMessage(data, 'http://localhost:9011/'); 
    // 触发父页面的message事件
    // parent.postMessage(data, '*'); 
}
</script>
  1. 跨域资源共享(CORS)

    CORS 通过新增一系列的HTTP头,让服务器能声明那些来源能访问该服务器上的资源,GET以外的请求,会以OPTIONS请求方式发一个预请求,从而得知服务器对资源请求支持的HTTP方法,在确认服务器允许跨域请求资源的情况下,以实际的HTTP请求方法发送真正的请求。

    1. 请求头:

      Origin :

      普通的HTTP请求也会带有,在CORS中专门作为Origin信息供后端比对,表明来源域。

      Access-Control-Request-Method :

      接下来请求的方法,例如PUT, DELETE等等

      Access-Control-Request-Headers :

      自定义的头部,所有用setRequestHeader方法设置的头部都将会以逗号隔开的形式包含在这个头中

    2. http响应头

      然后浏览器再根据服务器的返回值判断是否发送非简单请求。简单请求前面讲过是直接发送,只是多加一个origin字段表明跨域请求的来源。然后服务器处理完请求之后,会再返回结果中加上如下控制字段

      Access-Control-Allow-Origin :

      允许跨域访问的域,可以是一个域的列表,也可以是通配符"*"。这里要注意Origin规则只对域名有效,并不会对子目录有效。即http://foo.example/subdir/ 是无效的。但是不同子域名需要分开设置,这里的规则可以参照同源策略

      Access-Control-Allow-Credentials :

      是否允许请求带有验证信息,XMLHttpRequest请求的withCredentials标志设置为true时,认证通过,浏览器才将数据给脚本程序。

      Access-Control-Expose-Headers :

      允许脚本访问的返回头,请求成功后,脚本可以在XMLHttpRequest中访问这些头的信息

      Access-Control-Max-Age :

      缓存此次请求的秒数。在这个时间范围内,所有同类型的请求都将不再发送预检请求而是直接使用此次返回的头作为判断依据,非常有用,大幅优化请求次数

      Access-Control-Allow-Methods :

      允许使用的请求方法,以逗号隔开

      Access-Control-Allow-Headers :

      自定义的头部,以逗号隔开,大小写不敏感

  2. nginx代理跨域

  3. nodejs中间件代理跨域

  4. websocket协议跨域

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • 大家好,我是IT修真院郑州分院第一期的学员胡嘉杰,一枚正直纯洁善良的WEB前端程序员。 今天给大家分享一下,修真院...
    ithinker5阅读 492评论 0 1
  • 1. 什么是跨域? 跨域一词从字面意思看,就是跨域名嘛,但实际上跨域的范围绝对不止那么狭隘。具体概念如下:只要协议...
    w_zhuan阅读 510评论 0 0
  • JavaScript跨域总结与解决办法 什么是跨域 1、document.domain+iframe的设置 2、动...
    tom_123阅读 446评论 0 1
  • 1. 什么是跨域? 跨域一词从字面意思看,就是跨域名嘛,但实际上跨域的范围绝对不止那么狭隘。具体概念如下:只要协议...
    他在发呆阅读 822评论 0 0
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    他方l阅读 1,059评论 0 2