Middleware (6) — CORS

Feng Gao
8 min readOct 2, 2021

1 Introduction

CORS 是 Cross-Origin Resource Sharing 的缩写,它可以用来指定某个服务的资源可以被哪些请求的 Origin 使用,违反这个策略请求的响应会被浏览器拒绝掉。

假设现在我们有一段 Javascript 的代码运行在 https://domain-a.com 页面中,它会使用 XMLHttpRequest 向另一个服务 https://domain-b.com 发送请求来获取资源。

浏览器遵循同源的安全策略,也就是说发送给 domain-a.com 的服务的响应不会被限制,但是方位 domain-b.com 的请求就会遵循 CORS 的策略。

简单来说, CORS 就是通过 HTTP Header 来定义服务端来描述哪些 Origin 允许被浏览器读取。除此之外,对于那些可能对服务端产生副作用的请求,比如 POST 请求,浏览器会使用 Preflight 机制来判断是否满足 CORS 策略要求。

这里 ClientServer 发送了一个 GET 请求,在请求的 Header 中包含 Origin:foo.example 记录,服务端返回的 ResponseHeader 包含 Access-Control-Allow-Origin:* 它说明任何 Origin 都可以访问这个资源。也就是说如果值为 Access-Control-Allow-Origin: https://foo.example ,那么非 https://foo.example 的请求,浏览器就会拒绝返回的 Response

假设浏览器中的 Javascript 代码想要发送一个 POST 请求,由于 POST 请求可能产生一些 Side Effect , 所以浏览器会通过 Options 方法发送一个 preflight

我们看一下 preflight 的请求

OPTIONS /doc HTTP/1.1
Host: bar.other
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: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive

Options 的请求中, Access-Control-Request-Method 用来指明接下来的请求的方法,而 Access-Control-Request-Headers 说明了接下来的请求会包含 X-PINGOTHER, Content-Type 两个自定义的 Header

在响应中,

  • Access-Control-Allow-Origin: https://foo.example 说明的是允许 https://foo.example 访问。
  • Access-Control-Allow-Methods: POST, GET, OPTIONS 允许 POST , GETOPTIONS 三个方法访问。
  • Access-Control-Allow-Headers 确保 X-PINGOTHERSContent-Type 两个 Header 都可以访问。
  • Access-Control-Max-Age 则是说明多久这个 preflight 请求可以被缓存起来。

有时候访问第三方的资源的时候,需要带上 Credential , 比如说 Cookie ,那么 CORS 是如何如何定义这个策略的呢?

这里请求 Header 包含了 Cookie 这个字段;而响应中 Headers 包含了 Access-Control-Allow-Credentials:true ,如果没有这个字段,那么浏览器会忽略掉请求的响应的内容。

对于 Preflight 中使用 Credential 的话,有以下的限制条件

  • Access-Control-Allow-Origin 不能设置为 *
  • Access-Control-Allow-Headers 不能设置为 *
  • Access-Control-Allow-Methods 不能设置为 *

2 CorsPolicy

CorsPolicy 定义了我们在上面的讨论的所有的策略,包含了下面这些字段

CorsPolicyBuilder 是一个 CorsPolicy 的 Builder 模式的构造器,主要目的是构建一个 CorsPolicy 对象。

3 CorsOptions

对于服务端而言,不同的资源可能有不同的 Policy ,因此 CorsOptions 定了不同的 Policy 以供后续选择。

这里使用了 PolicyMap 字典保存所有的 Policy 对象,其中 KeyPolicy 的名字,而 Value(CorsPolicy, Task<CorsPolicy>) 元组。其中某一个默认的 Policy 名称 __DefaultCorsPolicy

ICorsPolicyProvider 接口则根据 policyNameCorsOption 中获取响应的 CorsPolicy 对象,如果 PolicyNamenull ,则使用 DefaultCorsPolicyName

4 CorsService

CorsService 主要做两件事

  • 根据当前的 HttpRequestCorsPolicy 来计算出 CorsResult
  • 根据之前获取的 CorsResult ,在 HttpResponse 中写入相关的信息

CorsResult 中的 IsOriginAllowed 字段,是由 IsOriginAllowed 决定的

有两种情况返回 true

  • policy 允许任何 Origin
  • policy 包含了 origin

ApplyResult 是将 CorsResult 写入到 ResponseHeader

注意如果 IsOriginAllowed=false ,则不会往 ResponseHeader 写入任何东西。

5. CorsMiddleware

CorsMiddleware 使用来添加 Cors 的中间件

主要流程如下

  1. 如果请求头不带有 Origin ,则跳过
  2. 如果请求命中的 EndPointIDisableCorsAttribute ,也就是意味着不进行 Cors 操作,那么对于 preflight 就直接返回,对于其他的请求则由 next 处理
  3. 对于 ICorsPolicyMetadataIEnableCorsAttributeEndpoint ,则获取 PolicyName 或者 CorsPolicy 对象
  4. 在获取 CorsPolicy 之后,则调用 CorsServiceHttpRequest 进行计算,如果是 preflight ,则立马 对 Response 进行修改,然后返回;对于其他的请求,注册一个操作的委托,然后注册到 ResponseOnStarting 方法中。

6 实例

--

--

Feng Gao

A software developer in Microsoft at Suzhou. Most articles spoken language is Chinese. I will try with English when I’m ready