Luna Tech | CORS

December 26, 2020 字数 1857 4 min


1. CORS

CORS 全称为 Cross-Origin Resource Sharing,是一个 HTTP 的功能,用来限制 web app 跨域名获取的资源。

资源的定义我们在讲 RESTful 的时候提到过,网页、图片、录像这些都是 resource。

假设你有一个服务器,服务器里面有图片、视频、文档之类的资源,你不希望把这些资源完全公开,那么就可以通过 CORS 来限制能获取到这些资源的域名。

比如你规定了只有 abc.com 发来的请求是 ok 的,那么当 xyz.com 发来资源获取请求时就会被服务器拒绝。

基本概念

  1. Origin: 请求的发起方
  2. Domain: 域名
  3. Simple Request: 简单请求
  4. CORS Header: CORS 头
  5. Preflighted requests: 先行请求
  6. Cache: 缓存

2. CORS 规则在什么情况下有效?

我们可能会理所当然地认为,只要在服务器那边设好 CORS 规则,所有资源请求都会先经过规则判定,再按照规则回复给请求方。

实际上,我们漏掉了 Simple Request(简单请求)这个概念,因为凡是 Simple Request,都不遵守 CORS 规则!!

1. 哪些 Request 算是 Simple Request 呢?

  1. embedding an image(插图)
  2. embedding a script(插脚本)
  3. linking to CSS(链接 CSS)
  4. form data submission(提交表单数据)

2. 为什么 Simple Request 不遵守 CORS 规则呢?

因为 Request 发出去的时候是不包含 Origin header 的,也就是说服务器根本不知道你这个请求是哪里来的。

假如请求不包含 Origin header,服务器设置的那些 CORS rule 就等于通通失效,即便回复的 header 包含 CORS rule,实际上也等于没有。

也就是说,是否遵守 CORS 规则,关键在于发请求的那一方有没有把这个请求当做简单请求,有没有发送 Origin header。

所有 CORS 请求都必须有 Origin header,没有这个 header,CORS 规则就没用; 因此,大部分的 server 只有在看到了 Origin header 的时候才会返回跟 CORS 有关的规则。

3. 尴尬了……那怎么保证请求方发送 Origin header 呢?

两种方法:

  1. 假如那个网站是你写的,那么可以在 html 里面添加 crossorigin 这个属性,就可以把简单请求复杂化;

    https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin

    1
    
    <img crossorigin scr="xxxxx">
    
    • crossorigin = crossorigin="" = crossorigin="anonymous"
    • 还有一种情况是 crossorigin="use-credentials"
  2. 假如那个网站获取资源的时候用的是 JavaScript,这个请求在默认情况下不是简单请求,会受到 CORS 规则的限制;

    https://developer.mozilla.org/en-US/docs/Web/API/Request/mode

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    // 默认是有 CORS 的,1和2效果一样
    // 1
    fetch("https://xxxx/image.png");
    // 2
    fetch("https://xxxx/image.png", {
      mode: "cors"
    });
    
    // 3 no-cors 不发送 origin header,忽略 CORS
    fetch("https://xxxx/image.png", {
      mode: "no-cors"
    });
    
    // 4 只能向同一个 origin 发起请求
    fetch("https://xxxx/image.png", {
      mode: "same-origin"
    });
    

4. CORS credential

CORS request 默认是匿名的,不需要发送 credential 的。你可以开启 credential,浏览器需要在发送请求的同时把 cookie 和 HTTP authentication 信息也一起给 server。

如何开启呢?Server response 要符合这两条要求:

  1. Access-Control-Allow-Origin: https://non-wild-card-origin - 用 wildcard 会导致请求失败
  2. Access-Control-Allow-Credentials: true - 这就是开启 credential 的配置

5. CORS 不是一个身份校验机制

CORS 有没有用,关键看请求方怎么做,所以千万别以为设定了 CORS 规则就万事大吉,该验证的时候还是要验证,该授权的时候还是要授权,尤其是一些敏感资源,一定要好好保护起来,不要随随便便就开放给所有人看~

CORS is not an authentication mechanism。

记住,CORS 规则需要 client 和 server 双方一起遵守,假如恶意用户发送的请求把 CORS 相关的 header 全部移除掉了,Server 也拿它没办法,所以我们需要加入其它的机制,比如 authentication,防止跨域名伪造请求的一些技巧(anti-cross-site request forgery tactics)来拦截恶意请求。


3. Common CORS Headers

Request Header

  1. Origin
  2. Access-Control-Request-Method
  3. Access-Control-Request-Headers

Response Header

  1. Access-Control-Allow-Origin

  2. Access-Control-Allow-Methods

  3. Access-Control-Allow-Headers

  4. Access-Control-Allow-Credentials

    是否需要发送 credential。

  5. Access-Control-Max-Age

    跟 CORS rule cache 有关。


4. 如何支持多个 origin

Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: https://abc.com
Access-Control-Allow-Origin: https://xyz*.com
// 无法实现
Access-Control-Allow-Origin: https://abc.com, https://xyz*.com

我们可以用 wildcard(通配符)来表示接受所有的 origin,或者是符合某种模式的 url,但是我们没法通过分隔符来接受多个 origin(上面第四种情况)。

一种可行的做法是:

在 server 里面写一些逻辑,比如把请求的 origin header 和我们的白名单进行比对,符合的就是合法的,不符合的就拒绝。

另外,用 Azure 的话这个问题可以比较方便的解决,因为写规则的时候可以用第四种方法来写,Azure 会按照规则进行判定;其他的 framework 应该也有相关的支持。


5. Preflighted Requests(先行请求)

CORS request 分为两步,第一步是先行请求,Client 带着实际的请求 Header 和 method 去问 server,这个请求你觉得可以通过不?

先行请求会得到 server 的回复,从 response header 里面可以知道什么样的 request 是满足要求的,假如先行请求成功了,Client 再发起实际的资源请求;假如先行请求失败了,就没必要去发起实际的请求啦~

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

CORS also relies on a mechanism by which browsers make a “preflight” request to the server hosting the cross-origin resource, in order to check that the server will permit the actual request. In that preflight, the browser sends headers that indicate the HTTP method and headers that will be used in the actual request.

Applied to outgoing requests that contain customer headers, non-simple content types, or methods like PUT, PATCH and DELETE.

Preflighted Request


6. 结语

本文主要讲了:

  1. CORS 的目的(保护资源,不被未经允许的 Client 获取);
  2. CORS 的局限性(Client 需要发送 Origin Header);
  3. Simple Request 为什么不遵守 CORS 规则;
  4. 保证 Client 发送 CORS 请求的两种方法(html crossorigin, script);
  5. Preflighted Request 的作用;
  6. Server 如何支持多个不同 origin;

希望对大家的学习有所帮助~​


Talk to Luna


Support Luna