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
策略要求。
这里 Client
向 Server
发送了一个 GET
请求,在请求的 Header
中包含 Origin:foo.example
记录,服务端返回的 Response
的 Header
包含 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-TypeHTTP/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
,GET
和OPTIONS
三个方法访问。Access-Control-Allow-Headers
确保X-PINGOTHERS
和Content-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
对象,其中 Key
是 Policy
的名字,而 Value
是 (CorsPolicy, Task<CorsPolicy>)
元组。其中某一个默认的 Policy
名称 __DefaultCorsPolicy
。
ICorsPolicyProvider
接口则根据 policyName
从 CorsOption
中获取响应的 CorsPolicy
对象,如果 PolicyName
为 null
,则使用 DefaultCorsPolicyName
4 CorsService
CorsService
主要做两件事
- 根据当前的
HttpRequest
和CorsPolicy
来计算出CorsResult
- 根据之前获取的
CorsResult
,在HttpResponse
中写入相关的信息
CorsResult
中的 IsOriginAllowed
字段,是由 IsOriginAllowed
决定的
有两种情况返回 true
- policy 允许任何
Origin
- policy 包含了
origin
ApplyResult
是将 CorsResult
写入到 Response
的 Header
中
注意如果 IsOriginAllowed=false
,则不会往 Response
的 Header
写入任何东西。
5. CorsMiddleware
CorsMiddleware
使用来添加 Cors
的中间件
主要流程如下
- 如果请求头不带有
Origin
,则跳过 - 如果请求命中的
EndPoint
是IDisableCorsAttribute
,也就是意味着不进行Cors
操作,那么对于preflight
就直接返回,对于其他的请求则由next
处理 - 对于
ICorsPolicyMetadata
和IEnableCorsAttribute
的Endpoint
,则获取PolicyName
或者CorsPolicy
对象 - 在获取
CorsPolicy
之后,则调用CorsService
对HttpRequest
进行计算,如果是preflight
,则立马 对Response
进行修改,然后返回;对于其他的请求,注册一个操作的委托,然后注册到Response
的OnStarting
方法中。