写点什么

安全系列之: 跨域资源共享 CORS

发布于: 25 分钟前

简介

什么是跨域资源共享呢? 我们知道一个域是由 scheme、domain 和 port 三部分来组成的,这三个部分可以唯一标记一个域,或者一个服务器请求的地址。跨域资源共享的意思就是服务器允许其他的域来访问它自己域的资源。

CORS 是一个基于 HTTP-header 检测的机制,本文将会详细对其进行说明。

CORS 举例

为了安全起见,一般一个域发起的请求只能获取该域自己的资源,因为域资源内部的互相调用被认为是安全的。

但是随着现代浏览器技术和 ajax 技术的发展,渐渐的出现了从 javascript 中去请求其他域资源的需求,我们把这样的需求叫做跨域请求。

比如说客户端从域http://www.flydean.com向域http://www.abc.com/data.json请求数据。

那么客户端是怎么知道服务器是否支持 CORS 的呢?

这里会使用到一个叫做 preflight 的请求,这个请求只是向服务器确认是否支持要访问资源的跨域请求,当客户端得到响应之后,才会真正的去请求服务器中的跨域资源。

虽然是客户端去设置 HTTP 请求的 header 来进行 CORS 请求,但是服务端也需要进行一些设置来保证能够响应客户端的请求。所以本文同时适合前端开发者和后端开发者。

CORS protocol

没错,任意一种请求要想标准化,那么必须制定标准的协议,CORS 也一样,CORS protocol 主要定义了 HTTP 中的请求头和响应头。我们分别来详细了解。

HTTP request headers

首先是 HTTP 的请求头。请求头是客户端请求资源时所带的数据。CORS 请求头主要包含三部分。

第一部分是 Origin,表示发起跨域资源请求的 request 或者 preflight request 源:

Origin: <origin>
复制代码

Origin 只包含 server name 信息,并不包含任何 PATH 信息。

注意,Origin 的值可能为 null

第二部分是 Access-Control-Request-Method,这是一个 preflight request,告诉服务器下一次真正会使用的 HTTP 资源请求方法:

Access-Control-Request-Method: <method>
复制代码

第三部分是 Access-Control-Request-Headers,同样也是一个 preflight request,告诉服务器下一次真正使用的 HTTP 请求中要带的 header 数据。header 中的数据是和 server 端的 Access-Control-Allow-Headers 相对应的。

Access-Control-Request-Headers: <field-name>[, <field-name>]*
复制代码

HTTP response headers

有了客户端的请求,还需要服务器端的响应,我们看下服务器端都需要设置那些 HTTP header 数据。

  1. Access-Control-Allow-Origin

Access-Control-Allow-Origin 表示服务器允许的 CORS 的域,可以指定特定的域,也可以使用*表示接收所有的域。

Access-Control-Allow-Origin: <origin> | *
复制代码

要注意的是,如果请求带有认证信息,则不能使用*。

我们看一个例子:

Access-Control-Allow-Origin: http://www.flydean.comVary: Origin
复制代码

上面例子表示服务器允许接收来自http://www.flydean.com的请求,这里指定了具体的某一个域,而不是使用*。因为服务器端可以设置一个允许的域列表,所以这里返回的只是其中的一个域地址,所以还需要在下面加上一个 Vary:Origin 头信息,表示 Access-Control-Allow-Origin 会随客户端请求头中的 Origin 信息自动发送变化。

  1. Access-Control-Expose-Headers

Access-Control-Expose-Headers 表示服务器端允许客户端或者 CORS 资源的同时能够访问到的 header 信息。其格式如下:

Access-Control-Expose-Headers: <header-name>[, <header-name>]*
复制代码

例如:

Access-Control-Expose-Headers: Custom-Header1, Custom-Header2
复制代码

上面的例子将向客户端暴露 Custom-Header1, Custom-Header2 两个 header,客户端可以获取到这两个 header 的值。

  1. Access-Control-Max-Age

Access-Control-Max-Age 表示 preflight request 的请求结果将会被缓存多久,其格式如下:

Access-Control-Max-Age: <delta-seconds>
复制代码

delta-seconds 是以秒为单位。

  1. Access-Control-Allow-Credentials

这个字段用来表示服务器端是否接受客户端带有 credentials 字段的请求。如果用在 preflight 请求中,则表示后续的真实请求是否支持 credentials,其格式如下:

Access-Control-Allow-Credentials: true
复制代码
  1. Access-Control-Allow-Methods

这个字段表示访问资源允许的方法,主要用在 preflight request 中。其格式如下:

Access-Control-Allow-Methods: <method>[, <method>]*
复制代码
  1. Access-Control-Allow-Headers

用在 preflight request 中,表示真正能够被用来做请求的 header 字段,其格式如下:

Access-Control-Allow-Headers: <header-name>[, <header-name>]*
复制代码

有了 CORS 协议的基本概念之后,我们就可以开始使用 CORS 来构建跨域资源访问了。

基本 CORS

先来看一个最基本的 CORS 请求,比如现在我们的网站是http://www.flydean.com,在该网站中的某个页面中,我们希望获取到https://google.com/data/dataA,那么我们可以编写的 JS 代码如下:

const xhr = new XMLHttpRequest();const url = 'https://google.com/data/dataA';
xhr.open('GET', url);xhr.onreadystatechange = someHandler;xhr.send();

复制代码

该请求是一个最基本的 CORS 请求,我们看下客户端发送的请求包含哪些数据:

GET /data/dataA HTTP/1.1Host: google.comUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-us,en;q=0.5Accept-Encoding: gzip,deflateConnection: keep-aliveOrigin: http://www.flydean.com
复制代码

这个请求跟 CORS 有关的就是 Origin,表示请求的源域是http://www.flydean.com

可能的返回结果如下:

HTTP/1.1 200 OKDate: Mon, 01 May 2021 00:23:53 GMTServer: Apache/2Access-Control-Allow-Origin: *Keep-Alive: timeout=2, max=100Connection: Keep-AliveTransfer-Encoding: chunkedContent-Type: application/xml
[…Data…]
复制代码

上面的返回结果要注意的是 Access-Control-Allow-Origin: *,表示服务器允许所有的 Origin 请求。

Preflighted requests

上面的例子是一个最基本的请求,客户端直接向服务器端请求资源。接下来我们看一个 Preflighted requests 的例子,Preflighted requests 的请求分两部分,第一部分是请求判断,第二部分才是真正的请求。

注意,GET 请求是不会发送 preflighted 的。

什么时候会发送 Preflighted requests 呢?

当客户端发送 OPTIONS 方法给服务器的时候,为了安全起见,因为服务器并不一定能够接受这些 OPTIONS 的方法,所以客户端需要首先发送一个 preflighted requests,等待服务器响应,等服务器确认之后,再发送真实的请求。我们举一个例子。

const xhr = new XMLHttpRequest();xhr.open('POST', 'https://google.com/data/dataA');flydeanxhr.setRequestHeader('cust-head', 'www.flydean.com');xhr.setRequestHeader('Content-Type', 'application/xml');xhr.onreadystatechange = handler;xhr.send('<site>www.flydean.com</site>');

复制代码

上例中,我们向服务器端发送了一个 POST 请求,在这个请求中我们添加了一个自定义的 header:cust-head。因为这个 header 并不是 HTTP1.1 中标准的 header,所以需要发送一个 Preflighted requests 先。

OPTIONS /data/dataA HTTP/1.1Host: google.comUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-us,en;q=0.5Accept-Encoding: gzip,deflateConnection: keep-aliveOrigin: http://www.flydean.comAccess-Control-Request-Method: POSTAccess-Control-Request-Headers: cust-head, Content-Type
复制代码

请求中添加了 Access-Control-Request-Method 和 Access-Control-Request-Headers 这两个多出来的字段。

得到的服务器响应如下:

HTTP/1.1 204 No ContentDate: Mon, 01 May 2021 01:15:39 GMTServer: Apache/2Access-Control-Allow-Origin: http://www.flydean.comAccess-Control-Allow-Methods: POST, GET, OPTIONSAccess-Control-Allow-Headers: cust-head, Content-TypeAccess-Control-Max-Age: 86400Vary: Accept-Encoding, OriginKeep-Alive: timeout=2, max=100Connection: Keep-Alive
复制代码

响应中返回了 Access-Control-Allow-Origin,Access-Control-Allow-Methods 和 Access-Control-Allow-Headers。

当客户端收到服务器的响应之后,发现配后续的请求,就可以继续发送真实的请求了:

POST /data/dataA HTTP/1.1Host: google.comUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-us,en;q=0.5Accept-Encoding: gzip,deflateConnection: keep-alivecust-head: www.flydean.comContent-Type: text/xml; charset=UTF-8Referer: http://www.flydean.com/index.htmlContent-Length: 55Origin: http://www.flydean.comPragma: no-cacheCache-Control: no-cache
<site>www.flydean.com</site>
复制代码

在真实的请求中,我们不需要再发送 Access-Control-Request*头标记了,只需要发送真实的请求数据即可。

最后,我们得到 server 端的响应:

HTTP/1.1 200 OKDate: Mon, 01 May 2021 01:15:40 GMTServer: Apache/2Access-Control-Allow-Origin: http://www.flydean.comVary: Accept-Encoding, OriginContent-Encoding: gzipContent-Length: 235Keep-Alive: timeout=2, max=99Connection: Keep-AliveContent-Type: text/plain
[Some data]
复制代码

带认证的请求

有时候,我们需要访问的资源需要带认证信息,这些认证信息是通过 HTTP cookies 来进行传输的,但是对于浏览器来说,默认情况下是不会进行认证的。要想进行认证,必须设置特定的标记:

const invocation = new XMLHttpRequest();const url = 'https://google.com/data/dataA';
function corscall() { if (invocation) { invocation.open('GET', url, true); invocation.withCredentials = true; invocation.onreadystatechange = handler; invocation.send(); }}

复制代码

上面的例子中,我们设置了 withCredentials flag,表示这是一个带认证的请求。

其对应的请求如下:

GET data/dataA HTTP/1.1Host: google.comUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-us,en;q=0.5Accept-Encoding: gzip,deflateConnection: keep-aliveReferer: http://www.flydean.com/index.htmlOrigin: http://www.flydean.comCookie: name=flydean
复制代码

请求中我们带上了 Cookie,服务器对应的响应如下:

HTTP/1.1 200 OKDate: Mon, 01 May 2021 01:34:52 GMTServer: Apache/2Access-Control-Allow-Origin: http://www.flydean.comAccess-Control-Allow-Credentials: trueCache-Control: no-cachePragma: no-cacheSet-Cookie: name=flydean; expires=Wed, 31-May-2021 01:34:53 GMTVary: Accept-Encoding, OriginContent-Encoding: gzipContent-Length: 106Keep-Alive: timeout=2, max=100Connection: Keep-AliveContent-Type: text/plain
[text/plain payload]
复制代码

服务器返回了 Access-Control-Allow-Credentials: true,表示服务器接收 credentials 认证,并且返回了 Set-Cookie 选项对客户端的 cookie 进行更新。

要注意的是如果服务器支持 credentials,那么返回的 Access-Control-Allow-Origin,Access-Control-Allow-Headers 和 Access-Control-Allow-Methods 的值都不能是*。

总结

本文简单介绍了 HTTP 协议中的 CORS 协议,要注意的是 CORS 实际上是 HTTP 请求头和响应头之间的交互。

本文已收录于 http://www.flydean.com/cors/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 25 分钟前阅读数: 8
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
安全系列之:跨域资源共享CORS