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

35次阅读

共计 7326 个字符,预计需要花费 19 分钟才能阅读完成。

简介

什么是跨域资源共享呢? 咱们晓得一个域是由 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.com
Vary: 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.1
Host: google.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://www.flydean.com

这个申请跟 CORS 无关的就是 Origin,示意申请的源域是 http://www.flydean.com。

可能的返回后果如下:

HTTP/1.1 200 OK
Date: Mon, 01 May 2021 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-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');flydean
xhr.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.1
Host: google.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://www.flydean.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: cust-head, Content-Type

申请中增加了 Access-Control-Request-Method 和 Access-Control-Request-Headers 这两个多进去的字段。

失去的服务器响应如下:

HTTP/1.1 204 No Content
Date: Mon, 01 May 2021 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: http://www.flydean.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: cust-head, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive

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

当客户端收到服务器的响应之后,发现配后续的申请,就能够持续发送实在的申请了:

POST /data/dataA HTTP/1.1
Host: google.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
cust-head: www.flydean.com
Content-Type: text/xml; charset=UTF-8
Referer: http://www.flydean.com/index.html
Content-Length: 55
Origin: http://www.flydean.com
Pragma: no-cache
Cache-Control: no-cache

<site>www.flydean.com</site>

在实在的申请中,咱们不须要再发送 Access-Control-Request* 头标记了,只须要发送实在的申请数据即可。

最初,咱们失去 server 端的响应:

HTTP/1.1 200 OK
Date: Mon, 01 May 2021 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: http://www.flydean.com
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-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.1
Host: google.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Referer: http://www.flydean.com/index.html
Origin: http://www.flydean.com
Cookie: name=flydean

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

HTTP/1.1 200 OK
Date: Mon, 01 May 2021 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: http://www.flydean.com
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: name=flydean; expires=Wed, 31-May-2021 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

服务器返回了 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/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

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

正文完
 0