前端跨域方案

前端几种跨域方案

背景

在百度实习的时候曾经遇到过一些多服务器交互的问题,多台服务器不同域名,请求其他服务器数据时肯定是有一些问题的。
受浏览器同源策略的限制,本域的js不能操作其他域的页面对象(比如DOM)。但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦。所以我们要通过一些方法使本域的js能够操作其他域的页面对象或者使其他域的js能操作本域的页面对象(iframe之间)。
这里需要明确的一点是:所谓的域跟js的存放服务器没有关系,比如baidu.com的页面加载了google.com的js,那么此js的所在域是baidu.com而不是google.com。也就是说,此时该js能操作baidu.com的页面对象,而不能操作google.com的页面对象。

方案

单向跨域(一般用于获取数据)

  • 使用JSONP跨域

    原理:因为通过script标签引入的js是不受同源策略的限制的(正如前文提到的baidu.com的页面加载了google.com的js)。所以我们可以通过script标签引入一个js或者是一个其他后缀形式(如PHP,jsp等)的文件,此文件返回一个js函数的调用,如返回JSONP_getUsers([“paco”,”john”,”lili”]),也就是说此文件返回的结果调用了JSONP_getUsers函数,并且把[“paco”,”john”,”lili”]传进去,这个[“paco”,”john”,”lili”]是一个用户列表。那么如果此时我们的页面中有一个JSONP_getUsers函数,那么JSONP_getUsers就被调用到,并且传入了用户列表。此时就实现了在本域获取其他域数据的功能,也就是跨域。
    简单来讲呢,就是前端首先实现一个方法,方法名要和服务器端规定好,然后后端传来数据为函数名并且携带参数,具体参数由前端处理。
    例如:
    前端引入远程js并定义好JSONP_getUsers函数,注意需要先定义好JSONP_getUsers函数,避免在远程js加载完成并调用JSONP_getUsers时,此函数不存在:

    //本域为baidu.com  
    <script>  
    function JSONP_getUsers(users){//定义好处理函数名  
        console.dir(users);  
    }  
    </script>  
    //加载google.com的getUsers.php  
    <script src="http://www.google.com/getUsers.php"></script> //script标签跨域
    

    服务端代码如下:

    <?php>  
    echo 'JSONP_getUsers(["paco","john","lili"])';//返回一个js函数的调用  
    ?>  
    

    为什么script标签引入的文件不受同源策略的限制?因为script标签引入的文件内容是不能够被客户端的js获取到的,不会影响到被引用文件的安全,所以没必要使script标签引入的文件遵循浏览器的同源策略。而通过ajax加载的文件内容是能够被客户端js获取到的,所以ajax必须遵循同源策略,否则被引入文件的内容会泄漏或者存在其他风险。

    JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求(虽然采用post+动态生成iframe是可以达到post跨域的目的,但这样做是一个比较极端的方式,不建议采用)。一般get请求能完成所有功能。比如如果需要给其他域服务器传送参数可以在请求后挂参数(注意不要挂隐私数据)

  • 动态创建script标签

    这种方法其实是JSONP跨域的简化版,JSONP只是在此基础上加入了回调函数。
    比如上例中的getUsers.php返回的如果不是一个js函数的调用,而是一个js变量,如:

    <?php>  
        echo 'var users=["paco","john","lili"]';//返回一个js变量users  
    ?>  
    

    那么在本域下就可以取到data变量,这里需要注意判断script节点是否加载完毕,如:

    js.onload = js.onreadystatechange = function() {  
        if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {  
            console.log(users);//此处取出其他域的数据  
            js.onload = js.onreadystatechange = null;  
        }  
    };  
    
  • 后端加跨域header

    这部需要在服务器这边处理

    nginx:

    add_header 'Access-Control-Allow-Origin' '*';
    

    php:

    header("Access-Control-Allow-Origin: http://www.baidu.com");//表示允许baidu.com跨域请求本文件  
    

总结

跨域非难事,只怕有心人