跨域解决方案整合

309 阅读7分钟

什么是跨域

浏览器有同源策略,它是浏览器最基本的安全功能。如若缺少了同源策略,浏览器容易受到XSS,CSRF攻击。所谓同源是指:协议 + 域名 + 端口 三者相同,即便两个不同的域名指向同一个ip地址也不算同源。 所谓跨域就是指浏览器允许向服务器发起跨域请求,从而克服ajax只能同源的使用限制。

URL的组成 协议 + 域名 + 端口 + 请求资源地址

Jsonp

JsonP原理

利用<script>标签没有跨域限制的漏洞,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

Jsonp的优缺点

优点是简单兼容性好,缺点是只支持get请求方法,容易遭受XSS攻击

Jsonp的实现

<script>
   var script = document.createElement('script');
   script.type = 'text/javascript';

   // 传参一个回调函数名给后端,请求资源成功后把资源返回并调用handleCallback
   script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
   document.head.appendChild(script);

   // 回调执行函数
   function handleCallback(res) {
       alert(JSON.stringify(res));
   }
</script>

CORS(跨域资源共享)

CORS是一个W3C标准,它允许浏览器向跨源服务器发出XHR请求,从而克服Ajax只能同源使用的限制。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

浏览器将CORS跨域请求分为简单请求和非简单请求。

简单请求:

  1. 使用下列方法之一:
  • head
  • get
  • post
  1. Content-Type 的值仅限于下列三者之一:
  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

实现方法:

  1. 在HTTP头信息中添加Origin字段 上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

  2. 在响应报文中设置Access-Control-Allow-Origin 值为可以访问当前资源的域,可以为*。

复杂请求 :

不满足简单请求的请求即为复杂请求,比如请求方法为Put或者Delete或者Content-type为application/json。非简单请求的CORS请求,会在正式通信之前,增加一个HTTP查询请求,简称为预检请求。

  • 预检请求:请求方法采用Options
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com //必须 表明来源
Access-Control-Request-Method: PUT //表示本次CORS请求会用到哪个请求方法
Access-Control-Request-Headers: X-Custom-Header 
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0..
  • 预检请求响应:服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method字段以后,确认允许跨源请求,就可以做出回应。

document.domain + iframe跨域

此方案仅限主域相同,子域不同的跨域应用场景。 实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

经典例子:

1.) 父窗口:(www.domain.com/a.html)

<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
    document.domain = 'domain.com'; 
    var user = 'admin';
</script>

2.)子窗口:(child.domain.com/b.html)

<script>
    document.domain = 'domain.com';
    // 获取父窗口中变量
    alert('get js data from parent ---> ' + window.parent.user);
</script>

修改document.domain的方法只适用于不同子域的框架间的交互。

location.hash跨域

因为父窗口可以对iframe进行URL读写,iframe也可以读写父窗口的URL,URL有一部分被称为hash,就是#号及其后面的字符,此方法的原理就是改变URL的hash部分来进行双向通信,这样做也存在缺点,诸如数据直接暴露在了url中,数据容量和类型都有限等

假如父页面是baidu.com/a.html,iframe嵌入的页面为google.com/b.html(此处省略了域名等url属性),要实现此两个页面间的通信可以通过以下方法。

  • a.html传送数据到b.html
  • a.html下修改iframe的src为google.com/b.html#paco
  • b.html监听到url发生变化,触发相应操作
  • b.html传送数据到a.html,由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于父窗口域名下的一个代理iframe
  • b.html下创建一个隐藏的iframe,此iframe的src是baidu.com域下的,并挂上要传送的hash数据,如src="www.baidu.com/proxy.html#…"
  • proxy.html监听到url发生变化,修改a.html的url(因为a.html和proxy.html同域,所以proxy.html可修改a.html的url hash)
  • a.html监听到url发生变化,触发相应操作

通过HTML5的postMessage方法实现跨域

这个功能主要包括接受信息的"message"事件和发送消息的"postMessage"方法,且是为数不多可以跨域操作的window属性之一

它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递

核心代码: otherWindow.postMessage(message, targetOrigin, [transfer]);

通过window.name跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

 // a.html(http://localhost:3000/b.html)
  <iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
  <script>
    let first = true
    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
    function load() {
      if(first){
      // 第1次onload(跨域页)成功后,切换到同域代理页面
        let iframe = document.getElementById('iframe');
        iframe.src = 'http://localhost:3000/b.html';
        first = false;
      }else{
      // 第2次onload(同域b.html页)成功后,读取同域window.name中数据
        console.log(iframe.contentWindow.name);
      }
    }
  </script>

通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

Node中间件代理(两次跨域)

实现原理:同源策略是浏览器需要遵循的标准,但如果是服务器向服务器发起请求,就不需要遵守同源策略。

代理服务器,需要做以下几个步骤:

  1. 接受客户端请求 。
  2. 将请求 转发给服务器。
  3. 拿到服务器 响应 数据。
  4. 将响应转发给客户端。

nginx反向代理

实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口

webSocket

Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。客户端与服务器建立连接后,即可互相请求。

总结:

以上就是9种常见的跨域解决方案,jsonp(只支持get请求,支持老的IE浏览器)适合加载不同域名的js、css,img等静态资源;CORS(支持所有类型的HTTP请求,但浏览器IE10以下不支持)适合做ajax各种跨域请求;Nginx代理跨域和nodejs中间件跨域原理都相似,都是搭建一个服务器,直接在服务器端请求HTTP接口,这适合前后端分离的前端项目调后端接口。document.domain+iframe适合主域名相同,子域名不同的跨域请求。postMessage、websocket都是HTML5新特性,兼容性不是很好,只适用于主流浏览器和IE10+。

参考文章:九种跨域方式实现原理(完整版)