前端跨域解决方案

前端跨域(规避同源策略)解决方案

什么是跨域?

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。
而我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。

同源策略

同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。目前,所有浏览器都实行这个策略。
所谓同源是指 “协议+域名+端口” 三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

同源策略的目的

同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

限制范围

随着互联网的发展,”同源政策”越来越严格。目前,如果非同源,共有三种行为受到限制。

  • Cookie、LocalStorage 和 IndexDB 无法读取。
  • DOM 无法获得
  • AJAX 请求不能发送

如何规避同源策略

  1. document.domain + iframe跨域
  2. location.hash + iframe
  3. window.name + iframe跨域
  4. postMessage跨域
  5. nginx代理跨域
  6. JSONP跨域
  7. WebSocket协议跨域
  8. 跨域资源共享(CORS)

postMessage

postMessage 是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

  1. 页面和其打开的新窗口的数据传递
  2. 多窗口之间消息传递
  3. 页面与嵌套的iframe消息传递
  4. 及上面三个 iframe 场景的跨域数据传递

A窗口的域名是 <http://example.com:8080>,以下是A窗口的script标签下的代码:

const popup = window.open(/*...popup details...*/);

// 如果弹出框没有被阻止且加载完成
// 这行语句没有发送信息出去,即使假设当前页面没有改变location(因为targetOrigin设置不对)
popup.postMessage("The user is 'bob' and the password is 'secret'", "https://secure.example.net");

// 假设当前页面没有改变location,这条语句会成功添加message到发送队列中去(targetOrigin设置对了)
popup.postMessage("hello there!", "http://example.org");

function receiveMessage(event) {
  // 我们能相信信息的发送者吗?  (也许这个发送者和我们最初打开的不是同一个页面).
  if (event.origin !== "http://example.org") return

  // event.source 是我们通过window.open打开的弹出页面 popup
  // event.data 是 popup发送给当前页面的消息 "hi there yourself!  the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);

弹出页 popup 域名是http://example.org,以下是script标签中的代码:

//当A页面postMessage被调用后,这个function被addEventListener调用
function receiveMessage(event) {
  // 我们能信任信息来源吗?
  if (event.origin !== "http://example.com:8080") return

  // event.source 就当前弹出页的来源页面
  // event.data 是 "hello there!"

  // 假设你已经验证了所受到信息的origin (任何时候你都应该这样做), 一个很方便的方式就是把event.source
  // 作为回信的对象,并且把event.origin作为targetOrigin
  event.source.postMessage("hi there yourself!  the secret response is: rheeeeet!", event.origin);
}

window.addEventListener("message", receiveMessage, false);

window.postMessage() 方法被调用时,会在所有页面脚本执行完毕之后(e.g., 在该方法之后设置的事件、之前设置的timeout 事件,etc.)向目标窗口派发一个 MessageEvent 消息。

用于接收消息的任何事件监听器必须首先使用origin和source属性来检查消息的发送者的身份。 这不能低估:无法检查origin和source属性会导致跨站点脚本攻击。

代理跨域

同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
所以通过 nginx 配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

AJAX

同源政策规定,AJAX请求只能发给同源的网址,否则就报错。

JSONP

JSONP是服务器与客户端跨源通信的常用方法。
优点是简单适用,老式浏览器全部支持,服务器改造非常小。而缺点就是只能实现get一种请求。

  1. 第一步网页动态插入 <script> 元素,由它向跨源网址发出请求。
    `javascript
    window.onload = function () {
    var script = document.createElement(‘script’);
    script.setAttribute(“type”,”text/javascript”);
    script.src = ‘http://example.com/ip?callback=fn';
    document.body.appendChild(script);
    }

function fn(data) {
console.log(‘后端返回的数据:’ + data);
};


2. 第二步服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
```javascript
fn({id: 1});

WebSocket

WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

# 浏览器发出的WebSocket请求的头信息
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

请求头存在Origin这个字段,服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

跨域资源共享(CORS)

CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。

请参考上一篇文章 跨域资源共享(CORS)

参考文章