跨域-取舍之道

545 阅读11分钟

前言

跨域问题是前端开发绕不过去的一个问题,常常让开发者很头疼,下面我们来聊聊跨域问题的由来,以及该怎么正确的解决跨域问题!

cors.webp

什么是跨域

首先思考一个问题,为什么会出现跨域?

在回答这个问题之前,我们先来看两个例子:

csrf

1、假如我用浏览器访问了站点A,站点A将一些私密信息存在了站点A域名的cookie下面

2、此时我用浏览器访问站点B,站点B的脚本获取到站点Acookie下面的信息,然后伪造了一个ajax请求,修改了用户的信息,这是一个非常危险的操作!

3、以上的整个过程就称为-跨域请求伪造(csrf),过程如下图所示:

image.png

跨域dom访问

1、假如我做了一个银行网站(假的),通过iframe将真正的银行网站链接到了我自己的网站中

2、此时银行用户在不知情的情况下,访问了我的银行网站,但是在真的银行站点中输入了自己的账户和密码

3、此时我通过自己的假网站访问真网站的DOM结构,获取用户的账号和密码,这也是很危险的

通过以上两个例子,我们知道,如果浏览器没有跨域策略存在,会存在许许多多的安全问题(不局限以上例子),这些问题影响很大,会造成不可预料的后果!

正是基于此,浏览器在1995年Netscape 公司引入浏览器提出了同源策略,借此来限制不同域之间资源的相互访问,提升网站的安全性。正是由于这个同源策略的存在,引入了浏览器跨域访问的问题。

那么什么是同源策略呢?它又是做到提升安全性的呢?我们接着往下看。

what is 同源策略

一直说同源,那么什么是同源呢?同源跟跨域有什么联系呢?

可以用一句话总结跨域与同源的关系--同源策略引入了跨域问题

那么浏览器是怎么定义同源策略的呢?很简单,我们先看下面一张图:

image.png

我们都知道,一个url地址,基本上由协议、域名、端口和路径组成,在定义同源时只用到了协议、域名和端口,顾名思义,判断一个两个站点是否同源,就只需要看他们的协议域名端口是否一样就可以了。图中有个简单的练习,大家可以看一下自己的掌握情况!

通过学习我们了解了浏览器的同源策略,它是浏览器的基本规则定义了跨域站点之间的资源交换协议规定跨域站点之间哪些资源可以交换,哪些资源不能交换。

在了解了同源策略以后,我们来看一下,同源策略对站点之间的资源访问都做了哪些限制:

同源策略规定:

(1) 跨域站点之间Cookie、LocalStorage 和 IndexDB 无法读取。

(2) 跨域站点之间DOM无法获得。

(3) 限制跨域站点之间AJAX请求。

通过学习同源策略,我们知道了不同站点(跨域,不同源)之间不能相互访问cookie、不能相互访问DOM,这也就解决了我们文章开头所遇到的安全问题。但跨域策略也是最基本的安全策略,并不能完全限制csrf等安全问题,这点需要注意!

同源策略是浏览器的一个安全策略,为了提高浏览器的安全性,但是它同时带来了跨域问题,所以同源策略是浏览器安全性和可用性之间的一个平衡点,有得也有舍(提高了安全性,牺牲了可用性),取舍之道也!

跨域例子

了解了跨域的由来之后,下面我们来看一下跨域的实际例子:

图片1.png

图一,我们从域https://test.dev.com.cn:4010访问到域https://vg-config-pre.vmic.xyz,被浏览器检查出ajax跨域请求,所以报错了!符合同源策略第三条!

图二与图三,我们从域https://test.dev.com.cn:4010访问到域https://vg-config-pre.vmic.xyz,我们看这个请求,发现请求与同源ajax之间的区别是它发送请求之时,没有携带cookie信息!这符合同源策略第一条!

至于同源策略第二条,我们可以通过内嵌iframe的例子,轻松想象!

跨域策略

浏览器出台了同源策略,提升了安全性,牺牲了可用性。为了提升不同域名之间的通信能力,浏览器给出了跨域的解决方案,那就是cors--跨域策略!

image.png

下面我们来了解一下什么是cors

对于cors,用官方的话来说是这样的:跨源资源共享 (CORS) (或通俗地译为跨域资源共享)是一种基于HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(域,协议和端口),这样浏览器可以访问加载这些资源。

我们来翻译一下这句话,cors是浏览器通过http头实现的一种跨域策略,浏览器可以通过http响应头规定允许除自己外的哪些域访问,并将跨域信息返回给浏览器。浏览器则可以http请求头携带自己的身份信息,方便服务器根据信息判断是否允许其跨域。

通过学习cors规则,我们了解到两个重点信息:

第一,跨域策略是通过http请求头http响应头告诉浏览器允不允许实现的

第二,跨域策略其实就是一问一答,浏览器通过http请求头问浏览器允不允许跨域访问,服务器通过http响应头告诉浏览器允不允许跨域访问

那跨域策略究竟规定了哪些请求头(requeset headers)响应头(response headers)呢?

请求头:

Origin: 表明请求的来源域名

header: 表明请求携带哪些请求头

method: 表明请求的方法

响应头:

Access-Control-Request-Method:表明服务端允许哪些方法跨域

Access-Control-Request-Headers:表明服务端允许携带哪些请求头跨域

Access-Control-Allow-origin:表明服务端允许哪些域跨域

Access-Control-Max-Age: 86400   :对于复杂请求来说,规定了预检请求的有效期,后面再说什么是复杂请求!

可以看到跨域请求头和响应头是一一对应的,如果请求头携带的信息都完全符合响应头的规则,则允许当次请求进行跨域访问。注意,是完全符合所有的条件!

以上就是cors跨域策略的主要内容。除了这些,cors还根据一些规则,对http请求进行了区分,分为简单请求复杂请求

简单请求与复杂请求

简单请求和复杂请求的跨域流程是不一样的,复杂请求简单请求多了一道预检请求。我们可以通过下图来看一下什么是简单请求:

image.png

可以看到,完全符合上面五个条件的被称为简单请求其余的都是复杂请求

简单请求跨域流程

简单请求的跨域流程是什么呢?直接发送跨域请求,判断请求头携带的跨域信息和是否符合响应头规定的跨域规则,如下图所示,请求的来源为Origin:foo.example,响应头Access-Control-Allow-origin:*规定所有域都可以跨域访问,完成整个跨域访问的流程。

image.png

image.png

复杂请求跨域流程

不符合上面五个条件的被称为复杂请求,下面的代码使用 POST 请求发送一个 XML 文档,该请求包含了一个自定义的请求首部字段(X-PINGOTHER: pingpong)。另外,该请求的 Content-Type 为 application/xml。因此,该请求的跨域流程需要首先发起“预检请求”。

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';

function callOtherDomain(){
  if(invocation)
    {
      invocation.open('POST', url, true);
      invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
      invocation.setRequestHeader('Content-Type', 'application/xml');
      invocation.onreadystatechange = handler;
      invocation.send(body);
    }
}

image.png

image.png

我们来看一下复杂请求的跨域流程,复杂请求会发送一个请求方法为options的预检请求,判断预检请求的请求头是否符合服务端响应头的跨域规则,如果符合才会继续发送正式请求,完成跨域流程。如下图所示,请求的来源为Origin:foo.example,响应头Access-Control-Allow-origin:http://foo.example规定foo.example可以跨域访问,浏览器接收到允许跨域的消息以后,再发送正式请求,完成跨域访问。复杂请求要增加预检请求的策略,可能是处于安全方面的考虑。

不管是复杂请求还是简单请求,最终都是判断请求头和是否符合响应头设置的跨域规则!,这也决定了解决跨域问题需要前端与服务端配合。

前文提到,跨域策略cors中还有Access-Control-Max-Age: 86400 这样一个请求头,它规定了预检请求的有效期,如果你在响应头中将该值设定为86400,那么预检请求的有效期就是86400ms,换算成一天24小时,24小时内跨域访问免预检,超过24小时需要重新发送预检请求。

这就是cors!

解决跨域

了解了同源和跨域策略之后,我们来了解一下该如何解决跨域问题!

思路有以下两种:

1、直接按照跨域需求,在服务端配置,多应用于测试环境、预发环境和线上环境

2、通过代理将不同域转化为相同域,开发环境使用居多

服务端配置解决跨域问题的方案,我们不在多说,属于是沟通问题。我们来看看怎么通过代理的方式,实现前端自己跨域访问。

开发环境--webpack devServer

直接利用webpack提供的代理,将ajax请求转换为和服务端站点一样的域名(消除跨域),具体实现如下,一般用于开发环境:

devServer: {
    host: 'vg-config-dev.vmic.xyz',
    proxy: {
      '/api-v0': {
        target: 'https://vg-palace-test.vivo.com.cn',
        changeOrigin: true,
        secure: true,
        logLevel: 'debug'
      }
    }
  }

假设我本地应用的域名为http://vg-config-dev.vmic.xyz:8080,我要访问的服务端接口为https://vg-palace-test.vivo.com.cn/api-v0/getConfig?id=20,这时出现了跨域访问,通过对/api-v0规则的代理,将http://vg-config-dev.vmic.xyz:8080代理到https://vg-palace-test.vivo.com.cn,当我访问http://vg-config-dev.vmic.xyz:8080/api-v0/getConfig?id=20时,实际上就是访问https://vg-palace-test.vivo.com.cn/api-v0/getConfig?id=20,而http://vg-config-dev.vmic.xyz:8080/api-v0/getConfig?id=20与我站点的域名http://vg-config-dev.vmic.xyz:8080同源,避免跨域访问,实现接口的正常访问。

开发环境--nginx

Nginx(发音同“engine X”)负载均衡,提供代理服务。

该软件由伊戈尔·赛索耶夫(Игорь Сысоев)创建并于2004年首次公开发布,俄罗斯。

2019年3月11日,Nginx公司被F5网络公司以6.7亿美元收购。

俄罗斯研发的产品,被美元收购,可以看到科学是没有国界的(玩笑)!!!

Nginx可以实现正向代理(代理客户端)反向代理(代理服务端),我们都知道vpn,我们通过vpn访问外国网站,就是正向代理的一个实际例子。我们就是利用Nginx的正向代理功能解决跨域问题,下面来介绍nginx实现跨域的具体流程。

niginx服务器 下载(mac)

1、先下载 homebrew,mac软件下载管理工具

2、使用homebrew下载 nginx, 命令行执行 brew install nginx 下载 nginx

3、下载switchhost

nginx-正向代理实现跨域

假设本地项目跑在http://10.15.51.236:8081上面,后端的跨域策略为:Access-Control-Allow-origin:.vivo.com.cn

我们使用 nginx 将本地项目http://10.15.51.236:8081代理到10.15.51.236:80,具体实现方式如下:

编辑 nginx 配置,命令行执行vim /usr/local/etc/nginx/nginx.conf,然后编辑文本:

avatar

然后开启 nginx 代理:命令行执行nginx命令

开启switchhost代理,将http://game-bbs.vivo.com.cn代理到http://10.15.51.236

avatar

我们知道,当直接访问域名不带端口号的时候,访问的是默认端口80,所以当我们访问http://game-bbs.vivo.com.cn时,通 switchhost 代理,实际上访问的是http://10.15.51.236,也就是就是访问http://10.15.51.236:80,再通过 nginx 代理,实际上就是访问http://10.15.51.236:8081

此时我们本地项目的域名变成了http://game-bbs.vivo.com.cn,符合了我们预设的接口跨域策略.vivo.com.cn,成功实现跨域!

nginx-反向代理实现跨域

代理服务端:该方案一般用于测乎环境、预发环境和正式环境。

image.png

反向代理的原理很简单,nginx代替服务端,设置了允许跨域的域名、headers和method,符合这些的条件的请求都可以跨域!

总结

跨域是浏览器同源策略带来的问题,同时浏览器也给出了跨域解决方案!

跨域问题是前端的事情(前端引入),但是解决跨域需要服务端或者运维配合!

参考文献

1、developer.mozilla.org/zh-CN/docs/…

2、developer.mozilla.org/zh-CN/docs/…

3、www.ruanyifeng.com/blog/2016/0…