网站外链发布平台,媒体发稿平台以资源+策略+技术的方式让品牌传播更简单高效!

10 种跨域解决方案(附终极方案)

作者:jcmp      发布时间:2021-04-29      浏览量:0
写在前面嗯。又来了,又说到跨域了,这是一

写在前面

嗯。又来了,又说到跨域了,这是一个老生常谈的话题,以前我觉得这种基础文章没有什么好写的,会想着你去了解底层啊,不是很简单吗。但是最近在开发一个 vscode 插件 发现,当你刚入门一样东西的时候,你不会想这么多,因为你对他不熟悉,当你遇到不会的东西,你就是想先找到解决方案,然后通过这个解决方案再去深入理解。就比如跨域,新人或者刚接触的人对它并不是那么熟悉,所以说列出一些自己积累的方案,以及一些常用的场景来给他人带来一些解决问题的思路,这件事是有意义的。(写完之后还发现真香。以后忘了还能回来看看)。

其实现在的环境对于刚入门的前端来说,非常的不友好,一方面吧,很多刚新人没有经历过工具的变更时代,另一方面框架的迭代更新速度很快。在以前你可能掌握几种常见的手法就好了。但是现在 webpack-dev-server 、 vue-cli 、 parcel ,这些脚手架都进行了一层封装,对于熟悉的人可能很简单,但是对于还未入门的人来说,简直就是一个黑盒,虽然用起来很方便,但是某一天遇到了问题,你对它不熟悉,你就会不知道所错。(但是别慌,主流 cli 的跨域方式我都会讲到)。

讲着讲着有点偏离方向,可能我讲的也并不一定是正确的。下面切入正题。

重要的说明: 在文中,web 端地址为 localhost:8000 服务端地址为 localhost:8080,这一点希望你能记住,会贯穿全文,你也可以把此处的两端的地址代入你自己的地址。

以下所有代码均在 https:// github.com/hua1995116/n ode-demo/tree/master/node-cors。

一、跨域是什么?

1.同源策略

跨域问题其实就是浏览器的同源策略所导致的。

当跨域时会收到以下错误

2.同源示例

那么如何才算是同源呢?先来看看 url 的组成部分

http://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument。

只有当

protocol(协议)、domain(域名)、port(端口)三者一致。

protocol(协议)、domain(域名)、port(端口)三者一致。

protocol(协议)、domain(域名)、port(端口)三者一致。

才是同源。

以下协议、域名、端口一致。

http://www.example.com:80/a.js

http://www.example.com:80/b.js

以下这种看上去再相似也没有用,都不是同源。

http://www.example.com:8080

http://www2.example.com:80

在这里注意一下啊,这里是为了突出端口的区别才写上端口。在默认情况下 http 可以省略端口 80, https 省略 443。这别到时候闹笑话了,你和我说 http://www.example.com:80 和 http://www.example.com 不是同源,他俩是一个东西。

http://www.example.com:80 === http://www.example.com。

https://www.example.com:443 === https://www.example.com。

唔,还是要说明一下。

二、如何解决跨域?

1.CORS

跨域资源共享( CORS ) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的 Web 应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器 不同的域、协议或端口 请求一个资源时,资源会发起一个 跨域 HTTP 请求 。

而在 cors 中会有 简单请求 和 复杂请求 的概念。

浏览器支持情况

当你使用 IE<=9, Opera<12, or Firefox<3.5 或者更加老的浏览器,这个时候请使用 JSONP 。

a.简单请求

不会触发 CORS 预检请求 。这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。若请求满足所有下述条件,则该请求可视为“简单请求”:

情况一: 使用以下方法(意思就是以下请求意外的都是非简单请求)。

情况二: 人为设置以下集合外的请求头

情况三: Content-Type 的值仅限于下列三者之一:(例如 application/json 为非简单请求)。

情况四:

请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。

情况五:

请求中没有使用 ReadableStream 对象。

b.非简单请求

除以上情况外的。

c.Node 中的解决方案

原生方式

我们来看下后端部分的解决方案。 Node 中 CORS 的解决代码.

第三方中间件

为了方便也可以直接使用中间件

关于 cors 的 cookie 问题

想要传递 cookie 需要满足 3 个条件

1.web 请求设置 withCredentials

这里默认情况下在跨域请求,浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进行传递 cookie .

2. Access-Control-Allow-Credentials 为 true。

3. Access-Control-Allow-Origin 为非 *

这里请求的方式,在 chrome 中是能看到返回值的,但是只要不满足以上其一,浏览器会报错,获取不到返回值。

d.前端示例

分别演示一下前端部分 简单请求 和 非简单请求

简单请求

非简单请求

这里我们加入了一个非集合内的 header 头 cc 来达到非简单请求的目的。

小结

1、 在新版的 chrome 中,如果你发送了复杂请求,你却看不到 options 请求。可以在这里设置 chrome://flags/#out-of-blink-cors 设置成 disbale ,重启浏览器。对于非简单请求就能看到 options 请求了。

2、 一般情况下后端接口是不会开启这个跨域头的,除非是一些与用户无关的不太重要的接口。

2.Node 正向代理

代理的思路为,利用服务端请求不会跨域的特性,让接口和当前站点同域。

代理前

代理后

这样,所有的资源以及请求都在一个域名下了。

a.cli 工具中的代理

1) Webpack (4.x)

在 webpack 中可以配置 proxy 来快速获得接口代理的能力。

修改前端接口请求方式,改为不带域名。(因为现在是同域请求了)

2) Vue-cli 2.x

3) Vue-cli 3.x

4) Parcel (2.x)

看到这里,心里一句 xxx 就会脱口而出,一会写配置文件,一会 proxyTable ,一会 proxy,怎么这么多的形式?学不动了学不动了。。。不过也不用慌,还是有方法的。以上所有配置都是有着共同的底层包 http-proxy-middleware .里面需要用到的各种 websocket , rewrite 等功能,直接看这个库的配置就可以了。关于 http-proxy-middleware 这个库的原理可以看我这篇文章 https:// github.com/hua1995116/p roxy 当然了。。。对于配置的位置入口还是非统一的。教一个搜索的技巧吧,上面配置写哪里都不用记的,想要哪个框架的 直接 google 搜索 xxx proxy 就行了。

例如 vue-cli 2 proxy 、 webpack proxy 等等....基本会搜到有官网的配置答案,通用且 nice。

b.使用自己的代理工具

cors-anywhere

服务端

前端代码

效果展示

缺点

无法专递 cookie,原因是因为这个是一个代理库,作者觉得中间传递 cookie 太不安全了。 https:// github.com/Rob--W/cors- anywhere/issues/208#issuecomment-575254153。

c.charles

介绍

这是一个测试、开发的神器。 介绍与使用

利用 charles 进行跨域,本质就是请求的拦截与代理。

在 tools/map remote 中设置代理

前端代码

后端代码

效果

访问 http:// localhost:8000/charles。

完美解决。

唔。这里又有一个注意点。在 Mac mojave 10.14 中会出现 charles 抓不到本地包的情况。这个时候需要自定义一个域名,然后配置 hosts 指定到 127.0.0.1 。然后修改访问方式 http://localhost.charlesproxy.com:8000/charles 。

3.Nginx 反向代理

介绍

Nginx 则是通过反向代理的方式,(这里也需要自定义一个域名)这里就是保证我当前域,能获取到静态资源和接口,不关心是怎么获取的。 nginx 安装教程。

配置下 hosts

127.0.0.1 local.test

配置 nginx

启动 nginx

sudo nginx

重启 nginx

sudo nginx -s reload

实现

前端代码

后端代码

效果

访问 http://local.test/charles

浏览器显示

4.JSONP

JSONP 主要就是利用了 script 标签没有跨域限制的这个特性来完成的。

使用限制

仅支持 GET 方法,如果想使用完整的 REST 接口,请使用 CORS 或者其他代理方式。

流程解析

1.前端定义解析函数(例如 jsonpCallback=function(){....})。

2.通过 params 形式包装请求参数,并且声明执行函数(例如 cb=jsonpCallback)。

3.后端获取前端声明的执行函数(jsonpCallback),并以带上参数并调用执行函数的方式传递给前端。

使用示例

后端实现

普通 js 示例

JQuery Ajax 示例

原理解析

其实这就是 js 的魔法

我们先来看最简单的 js 调用。嗯,很自然的调用。

我们稍稍改造一下,外链的形式。

我们再改造一下,我们把这个外链的 js 就当做是一个动态的接口,因为本身资源和接口一样,是一个请求,也包含各种参数,也可以动态化返回。

你仔细品,细细品,是不是 jsonp 有的优势就是 script 加载 js 的优势,加载的方式只不过换了一种说法。这也告诉我们一个道理,很多东西并没有那么神奇,是在你所学的知识范围内。就好比,桃树和柳树,如果你把他们当成很大跨度的东西去记忆理解,那么世上这么多树,你真的要累死了,你把他们都当成是树,哦吼?你会突然发现,你对世界上所有的树都有所了解,他们都会长叶子,光合作用....当然也有个例,但是你只需要去记忆这些细微的差别,抓住主干。。。嗯,反正就这么个道理。

5.Websocket

WebSocket 规范定义了一种 API,可在网络浏览器和服务器之间建立“套接字”连接。简单地说:客户端和服务器之间存在持久的连接,而且双方都可以随时开始发送数据。详细教程可以看 https://www. html5rocks.com/zh/tutor ials/websockets/basics/

这种方式本质没有使用了 HTTP 的响应头, 因此也没有跨域的限制,没有什么过多的解释直接上代码吧。

前端部分

后端部分

6.window.postMessage

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

用途

1.页面和其打开的新窗口的数据传递

2.多窗口之间消息传递

3.页面与嵌套的 iframe 消息传递

用法

详细用法看 https:// developer.mozilla.org/z h-CN/docs/Web/API/Window/postMessage。

otherWindow.postMessage(message, targetOrigin, [transfer]);

示例

index.html

another.html

7.document.domain + Iframe

从第 7 种到第 9 种方式,我觉得别人的写的已经很好了,为了完整性,我就拿别人的了。如有雷同....(不对,就是雷同....)不要说不出来。

该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式 。 只需要给页面添加 document.domain ='test.com' 表示二级域名都相同就可以实现跨域。

8.window.location.hash + Iframe。

实现原理

原理就是通过 url 带 hash ,通过一个非跨域的中间页面来传递数据。

实现流程

一开始 a.html 给 c.html 传一个 hash 值,然后 c.html 收到 hash 值后,再把 hash 值传递给 b.html,最后 b.html 将结果放到 a.html 的 hash 值中。 同样的,a.html 和 b.htm l 是同域的,都是 http://localhost:8000 ,而 c.html 是 http://localhost:8080。

9.window.name + Iframe

window 对象的 name 属性是一个很特别的属性,当该 window 的 location 变化,然后重新加载,它的 name 属性可以依然保持不变。

其中 a.html 和 b.html 是同域的,都是 http://localhost:8000 ,而 c.html 是 http://localhost:8080。

b.html 为中间代理页,与 a.html 同域,内容为空。

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

10.浏览器开启跨域(终极方案)

其实讲下其实跨域问题是浏览器策略,源头是他,那么能否能关闭这个功能呢?

答案是肯定的。

注意事项: 因为浏览器是众多 web 页面入口。我们是否也可以像客户端那种,就是用一个单独的专门宿主浏览器,来打开调试我们的开发页面。例如这里以 chrome canary 为例,这个是我专门调试页面的浏览器,不会用它来访问其他 web url。因此它也相对于安全一些。当然这个方式,只限于当你真的被跨域折磨地崩溃的时候才建议使用以下。使用后,请以正常的方式将他打开,以免你不小心用这个模式干了其他的事。

Windows

Mac

~/Downloads/chrome-data 这个目录可以自定义.

效果展示

嗯,使用起来很香,但是再次提醒,一般情况千万别轻易使用这个方式,这个方式好比七伤拳,使用的好威力无比,使用不好,很容易伤到自己。

三、为什么需要跨域?

在最一开始,我们知道了,跨域只存在于浏览器端。而浏览器为 web 提供访问入口。我们在可以浏览器内打开很多页面。正是这样的开放形态,所以我们需要对他有所限制。就比如林子大了,什么鸟都有,我们需要有一个统一的规范来进行约定才能保障这个安全性。

1.限制不同源的请求

这里还是用最常用的方式来讲解,例如用户登录 a 网站,同时新开 tab 打开了 b 网站,如果不限制同源, b 可以像 a 网站发起任何请求,会让不法分子有机可趁。

2.限制 dom 操作

我举个例子吧, 你先登录下 www.baidu.com ,然后访问我这个网址。

https:// zerolty.com/node-demo/i ndex.html。

你会发现,这个和真实的百度一模一样,如果再把域名搞的相似一些,是不是很容易被骗,如果可以进行 dom 操作...那么大家的信息在这种钓鱼网站眼里都是一颗颗小白菜,等着被收割。

写在最后

以上最常用的就是前 4 种方式,特别是第 2 种非常常见,我里面也提到了多种示例,大家可以慢慢消化一下。希望未来有更加安全的方式来限制 web ,解决跨域的头疼,哈哈哈哈。

有一个不成熟的想法,可以搞这么一个浏览器,只让访问内网/本地网络,专门给开发者用来调试页面用,对于静态资源可以配置白名单,这样是不是就没有跨域问题了,23333。上述如有错误,请第一时间指出,我会进行修改,以免给大家来误导。