工作中我们经常会遇到ajax跨域的问题,今天我们了解一下跨域的原因和各种解决方案

跨域原因


跨域解决方案(一)_cros


所谓跨域就是:在a.com域下,通过ajax请求访问b.com域下的资源,出于安全的考虑,浏览器同源策略允许跨域写,而不允许跨域读,写就是上行,发送请求,send request,读就是下行,接受响应,receive response;

通俗点就是:a域向b域发送ajax请求,可以请求成功,但在请求的结果返回时,浏览器会先去判断a域与b域是否是同一个域下,“是”则返回数据,“否”则拦截数据并抛出如下异常信息

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.


解决方案


主流解决方案有

  • jsonp

  • 后台代理

  • cors

其他解决方案有

  • tomcat设置过滤器(基于cors)

  • nginx设置(基于cors)

  • chrome通过命令屏蔽跨域安全检查

  • chorme通过插件实现跨域(基于cors)

JSONP

  • 原理:

    浏览器的同源策略中虽然限制了不同域之间无法通过ajax请求得到响应结果,但有些html标签可以突破该限制,如img,srcipt,iframe标签中的src属性,可以引用不属于本域的资源且执行成功,jsonp的原理就是利用src属性的特性,动态的创建这些标签发送请求,接收响应后执行回调

  • 案例:在a域中向b域发送ajax请求

      a域:前端js

       //定义回调函数    
       function myCallBack(data){   
            alert("回调的数据为:"+data);
       } 
     
      //按钮点击事件
      function btnClick(){  
          //创建script标签  
          var script = document.createElement('script');   
          //b域的资源路径   
          script.src = 'http://www.b.com/getData?callback=myCallBack';   
          // 在文档中追加script标签,开始发送请求到后台,响应结果后会自动调用myCallBack   
          document.body.appendChild(script);
      }

 a域:html

 <input type="button" onclick="btnClick" value="请求资源"/>

b域:java后端代码 

    //映射请求路径    
    @RequestMapping("/getData")    
    @ResponseBody    
    public String getData(String callback){        
        //获取回调函数名后,拼接函数加数据返回        
        return callback+"('访问成功')";   
    }

  • 流程:

    1.a域前端定义回调函数和点击事件

    2.当a域用户点击按钮后,触发点击事件,创建script对象且向b域后端发送请求

    3.当b域后端接收回调函数名后,拼接结果返回

    4.执行a域前端中的回调函数


  • 优点:

    1. 环境依赖少,只需要浏览器兼容js就可以执行

    2.兼容性强,基本所有的浏览器都可以采用该方案(包括IE6,7等老式浏览器)

  • 缺点:

    1.只能执行get类型http请求,非get请求无法采用该方案

    2.需要b域后端代码配合,侵入性强,在b域后端代码不可控的情况下无法采用

  • 其他:

    jquery中的jsonp原理与上面案例类似,只是在发送请求的过程中会帮我们动态创建回调函数,在调用完成后删除回调函数减少内存消耗,当然也可以通过设置指定回调函数名称传给后台

后台代理


原理:http请求可以正常访问跨域的资源,如果a域想访问b域的资源,可以在a域中的前端发送ajax请求到a域的后端,在a域的后端中发送http请求到b域获取资源,然后将资源返回给a域前端


案例:

a域:html

 <input type="button" onclick="btnClick" value="请求资源"/>

a域:前端js

    <script src="/javascripts/jquery.js" type="text/javascript"></script>
    <script type="text/javascript">
 
        //按钮点击事件
        function btnClick(){
            //发送ajax请求到a域自己的后端
            $.post("http://www.a.com/getData",function(data){
                alert(data);
            });
        }
    </script>

a域:后端代码(java)

 //映射请求路径
    @RequestMapping("/getData")
    @ResponseBody
    public String getData(){
        CloseableHttpClient httpClient= null;
        String result=null;
        try {
                httpClient= HttpClients.createDefault();
                HttpGet getMethod=new HttpGet(new URI("http://www.b.com/getData"));
                //请求b域资源 得到响应对象
                HttpResponse response= httpClient.execute(getMethod);
                result= EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (IOException e) {
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(httpClient!=null){
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //将b域资源返回给a域
            return result;
        }
    }

 b域:后端代码(java)

    //映射请求路径
    @RequestMapping("/getData")
    @ResponseBody
    public String getData(){
        /**
         * 省略业务逻辑代码
         */
       return "success";
    }


  • 流程

    1.用户在a域点击按钮,触发点击事件,向a域后端发送ajax请求

    2.a域后端发送http请求到b域后端请求资源

    3.请求b域资源成功后将结果返回a域前端,a域前端输出资源

  • 优点:

    1.可以使用getpost等多种请求方式

    2.b域后端不用配合a域做返回结果的修改

  • 缺点:

    1.性能较低,a域每一次请求b域资源,本质会发送2次请求,1次是发送ajax请求到a域自己后端,1次是a域后端发送请求到b域

    2.代码复杂,不利于维护。当a域b域请求资源比较多的情况下,每一个请求都要写一个后端代理,在代码量和维护上都是一个考验,该方案只适合跨域请求资源少,且b域不可控(即无法控制b域后端拼接结果返回)的情况下使用