参考链接:
前端常见跨域解决方案
跨域资源共享 CORS 详解(转自阮一峰博客)
跨域是指一个域下的文档或脚本视图去请求另一个域下的资源。

广义的跨域:

  1. 资源跳转: A链接、重定向、表单提交
  2. 资源嵌入: link、script、img、frame等dom标签,还有样式中background:url()、@font-face()等文件外链
  3. 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等

狭义的跨域:

是由浏览器同源策略限制的一类请求场景。

什么是同源策略

参考链接: 浏览器同源政策及其规避方法
1、同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略)。
2、浏览器安全的基石是同源政策。同源政策的目的是为了保证用户信息的安全,防止恶意的网站窃取数据。

同源策略限制以下几种行为:

  1. Cookie、LocalStorage 和 IndexDB 无法读取
  2. DOM 和 Js对象无法获得
  3. AJAX 请求不能发送

跨域解决方案

1、 通过jsonp跨域---ajax---服务器和客户端跨源通信----只可以进行get请求
2、 document.domain + iframe跨域-------主域一样子域不同,共享cookie
3、 location.hash + iframe------对于完全不同源的网站
4、 window.name + iframe跨域-----对于完全不同源的网站
5、 postMessage跨域--------------对于完全不同源的网站----localStorage---html5标签
6、 跨域资源共享(CORS)----ajax的根本跨源方法
7、 nginx代理跨域
8、 nodejs中间件代理跨域
9、 WebSocket协议跨域---ajax

1、通过jsonp跨域----ajax跨域---只能实现get请求

  • JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
  • 它的基本思想是,网页通过添加一个script元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
  • 首先,网页动态插入script元素,由它向跨源网址发出请求。
function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function () {
  //请求的查询字符串有一个callback参数,用来指定回调函数的名字
  addScriptTag('http://example.com/ip?callback=foo');
}
//服务器收到这个请求以后,会将数据放在回调函数的参数位置返回
function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};

2、 document.domain + iframe跨域

  • Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。

  • 这种方法只适用于cookie和iframe窗口,LocalStorage 和 IndexDB 无法通过这种方法

  • 只用于主域相同,子域不同

  • A页面中主域为example.com子域为w1,所以只是子域不同
    图片说明

  • 实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
    在document.domain中设置同一个域

  • 另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。

    Set-Cookie: key=value; domain=.example.com; path=/
    这样的话,二级域名和三级域名不用做任何设置,都可以读取这个cookie
    
    
    

3、 location.hash + iframe

针对完全不同源的网页
片段标识符指的是,url的#号后面的部分,如果只是改变片段标识符,页面不会重新刷新。
父窗口可以把信息写入子窗口的片段标识符

var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;

子窗口通过监听hashchange事件得到通知

window.onhashchange = checkMessage;
function checkMessage() {
  var message = window.location.hash;
  // ...
}

同样的,子窗口也可以改变父窗口的片段标识符。
parent.location.href= target + "#" + hash;

  • 实现原理: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

  • 具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。

4、 window.name + iframe跨域

  • 如果两个网站完全不同源,就无法拿到对方的DOM
  • 浏览器窗口有window.name属性,最大特点是:无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
    图片说明

这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。

参考代码:https://www.cnblogs.com/zhuzhenwei918/p/7403796.html

5、 postMessage跨域----html5的api(跨文档通信api)

  • 可以读取dom和localStorage

用于解决:

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

dom

参考代码:https://www.cnblogs.com/dolphinX/p/3464056.html

localStorage

  • 主窗口写入iframe子窗口的localStorage
    子窗口将父窗口发来的信息写入自己的localStorage
    window.onmessage = function(e) {
    if (e.origin !== 'http://bbb.com') {
    return;
    }
    var payload = JSON.parse(e.data);
    localStorage.setItem(payload.key, JSON.stringify(payload.data));
    };
    图片说明
    父窗口发送消息的代码:
    var win = document.getElementsByTagName('iframe')[0].contentWindow;
    var obj = { name: 'Jack' };
    win.postMessage(JSON.stringify({key: 'storage', data: obj}), 'http://bbb.com');
    ![图片说明](https://uploadfiles.nowcoder.com/images/20200324/473630826_1585021535033_D70B9FCB3A2AD74B2E63FD7B9CE4D7F5 "图片标题") 
    
    

6、CORS

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
几乎所有的浏览器ajax请求都是基于CORS机制的
CORS需要浏览器和服务器同时支持,整个CORS通信过程中,都是浏览器自动完成,不需要用户参与。浏览器一旦发现ajax请求跨源,就会自动添加一些附加的头信息。
实现CORS通信的关键是服务器,只要服务器实现了CORS端口,就可以跨源通信

CORS请求----是跨源ajax请求的根本方法

图片说明

简单请求

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。
Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

Access-Control-Allow-Origin:必须的,值要么是请求时origin字段的值,要么是一个*,表示接受任意域名的请求。
Access-Control-Allow-Credentials:可选,是一个布尔值,表示是否允许发送cookie
Access-Control-Expose-Headers:该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

要能发送cookie,不仅要指定Access-Control-Allow-Credentials: true,ajx请求也要打开
图片说明
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

非简单请求

4.1 预检请求

  • 非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

  • 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

  • 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
    图片说明
    上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header。

浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。
"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

浏览器的正常请求和回应
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

CORS与JSONP的使用目的相同,但是比JSONP更强大。

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

7、 nginx代理跨域

8、 nodejs中间件代理跨域

9、 WebSocket协议跨域---不实行同源策略

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

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,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

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