前端面试查漏补缺–(三) 跨域及常见解决办法-射手猫的个人博客

概述

因为浏览器出于安全考虑,有了同源策略,这样就导致只要协议、域名、端口有任何一个不同,都被当作是不同的域。也就是说,如果协议、域名或者端口有一个不同就是跨域,Ajax 请求就会失败。

常见跨域的解决办法

JSONP

JSONP 的原理很简单,就是利用 <script> 标签没有跨域限制的漏洞。通过 <script> 标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时。

<script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script>
<script>
    function jsonp(data) {
        console.log(data)
    }
</script>

JSONP 使用简单且兼容性不错,但是只限于 get 请求。

在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的,这时候就需要自己封装一个 JSONP,以下是简单实现

function jsonp(url, jsonpCallback, success) {
  let script = document.createElement('script')
  script.src = url
  script.async = true
  script.type = 'text/javascript'
  window[jsonpCallback] = function(data) {
    success && success(data)
  }
  document.body.appendChild(script)
}
jsonp('http://xxx', 'callback', function(value) {
  console.log(value)
})

CORS

CORS(Cross-Origin ResourceSharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。

CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。 目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。 因为需要通过 XDomainRequest 来实现。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。

postMessage

这是由H5提出来的的API,IE8以上支持这个功能。window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

window.postMessage() 方法被调用时,会在所有页面脚本执行完毕之后,向目标窗口派发一个 MessageEvent 消息。

该MessageEvent消息有四个属性需要注意:

  • message 属性表示该message 的类型;
  • data 属性为 window.postMessage 的第一个参数;
  • origin 属性表示调用window.postMessage() 方法时调用页面的当前状态;
  • source 属性记录调用 window.postMessage() 方法的窗口信息。

语法

otherWindow.postMessage(message, targetOrigin);

  • otherWindow:指目标窗口,也就是给哪个window发消息,是 window.frames 属性的成员或者由 window.open 方法创建的窗口
  • message: 是要发送的消息,类型为 String、Object (IE8、9 不支持)
  • targetOrigin: 是限定消息接收范围,不限制请使用 '*

例子:

A页面通过postMessage方法发送消息:

window.onload = function() {  
    var ifr = document.getElementById('ifr');  
    var targetOrigin = "http://www.google.com";  
    ifr.contentWindow.postMessage('hello world!', targetOrigin);  
};

B页面通过message事件监听并接受消息:

var onmessage = function (event) {  
  var data = event.data;//消息  
  var origin = event.origin;//消息来源地址  
  var source = event.source;//源Window对象  
  if(origin=="http://www.baidu.com"){  
console.log(data);//hello world!  
  }  
};  
if (typeof window.addEventListener != 'undefined') {  
  window.addEventListener('message', onmessage, false);  
} else if (typeof window.attachEvent != 'undefined') {  
  //for ie  
  window.attachEvent('onmessage', onmessage);  
}

nginx代理跨域

跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。

实现思路: 通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

nginx具体配置:

#proxy服务器
server {
    listen       81;
    server_name  www.domain1.com;

    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;

        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true;
    }
}

WebSocket与NodeJs中间件跨域等其他方法

这些方法除了nodejs用得都不是特别多,WebSocket不会专门用来做跨域,而是作为消息推送或者聊天等.

其余的方法,详细可以参考: 前端常见跨域解决方案(全)