跨域资源共享CORS
1. 跨域资源共享CORS介绍
1.1 两种验证模式
跨域资源共享 CORS 的验证机制分两种模式:简单请求和预先请求。
当请求同时满足下面三个条件时,CORS 验证机制会使用简单模式进行处理。
- 请求方法是下列之一:
- GET
- HEAD
- POST
- 请求头中的 Content-Type 请求头的值是下列之一:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- Fetch规范定义了CORS安全头的集合(跨域请求中自定义的头属于安全头的集合)该集合为:
- Accept
- Accept-Language
- Content-Language
- Content-Type (需要注意额外的限制)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
否则CORS验证机制会使用预先请求模式进行处理。
1.2 简单请求模式
简单请求模式下,浏览器直接发送跨域请求,并在请求头中携带 Origin 的头,表明这是一个跨域的请求。服务器端接到请求后,会根据自己的跨域规则,通过 Access-Control-Allow-Origin 和 Access-Control-Allow-Methods 响应头,来返回验证结果。
简单请求模式
应答中携带了跨域头 Access-Control-Allow-Origin。使用 Origin 和 Access-Control-Allow-Origin 就能完成最简单的访问控制。本例中,服务端返回的 Access-Control-Allow-Origin: *
表明,该资源可以被任意外域访问。如果服务端仅允许来自 http://cloud.baidu.com
的访问,该首部字段的内容如下:
Access-Control-Allow-Origin: http://cloud.baidu.com
现在,除了 http://cloud.baidu.com
,其它外域均不能访问该资源。
1.3 预先请求模式
浏览器在发现页面发出的请求非简单请求,并不会立即执行对应的请求代码,而是会触发预先请求模式。预先请求模式会先发送 Preflighted requests(预先验证请求),Preflighted requests 是一个 OPTIONS 请求,用于询问要被跨域访问的服务器,是否允许当前域名下的页面发送跨域的请求。在得到服务器的跨域授权后才能发送真正的 HTTP 请求。
OPTIONS 请求头部中会包含以下头部:Origin、Access-Control-Request-Method、Access-Control-Request-Headers。服务器收到 OPTIONS 请求后,设置 Access-Control-Allow-Origin、Access-Control-Allow-Method、Access-Control-Allow-Headers、Access-Control-Max-Age 头部与浏览器沟通来判断是否允许这个请求。如果 Preflighted requests 验证通过,浏览器才会发送真正的跨域请求。
预先请求模式
请求中的跨域头 Access-Control-Request-Method 告知服务器,实际请求将使用 GET 方法。请求中的跨域头 Access-Control-Request-Headers 告知服务器,实际请求将携带两个自定义请求首部字段:x-ca-nonce 与 content-type。服务器据此决定该实际请求是否被允许。
应答中的跨域头 Access-Control-Allow-Methods 表明服务器允许客户端使用 GET 方法发起请求。值为逗号分割的列表。
应答中的跨域头 Access-Control-Allow-Headers 表明服务器允许请求中携带字段 x-ca-nonce 与 content-type。与 Access-Control-Allow-Methods 一样,Access-Control-Allow-Headers 的值为逗号分割的列表。
应答中的跨域头 Access-Control-Max-Age 表明该响应的有效时间为 86400 秒,也就是 24 小时。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。
2. 在 API 网关中实现跨域资源共享
2.1 实现简单请求模式
API 网关默认所有 API 允许跨域访问,因此如果用户的 API 后端服务的应答中不做特殊返回,API 网关会返回允许所有域跨域访问的相关头。
下面是一个示例:
客户端的 API 请求
GET /simple HTTP/1.1
Host: bdcloudapi.com
orgin: cloud.baidu.com
content-type: application/x-www-form-urlencoded; charset=utf-8
accept: application/json; charset=utf-8
date: Tue, 05 Nov 2019 11:04:10 GMT
后端服务应答
HTTP/1.1 200 OK
Date: Tue, 05 Nov 2019 11:04:10 GMT
Content-Type: application/json; charset=UTF-8
Content-Length: 12
{"200","OK"}
API网关应答
HTTP/1.1 200 OK
Date: Tue, 05 Nov 2019 11:04:10 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH
X-Bce-Request-Id: 63c4a3a9-8636-4510-a54f-595fa74069e4
Content-Type: application/json; charset=UTF-8
Content-Length: 12
{"200","OK"}
从上面三个报文可以看出,API网关会对用户的后端服务应答做一定修改,增加两个跨域头:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH
这个跨域头的意思是,本 API 允许所有域的请求访问,允许跨域的请求方法包括请求头中列出的七种。
如果用户需要定制针对简单请求的应答的跨域头,只需要在后端服务应答中,增加 Access-Control-Allow-Origin 这个跨域头即可,后端服务应答中的头会默认覆盖掉API网关自己增加的头。下面是一个例子,这个例子中的 API 只允许 cloud.baidu.com 这一个域访问:
客户端的 API 请求
GET /simple HTTP/1.1
Host: bdcloudapi.com
orgin: cloud.baidu.com
content-type: application/x-www-form-urlencoded; charset=utf-8
accept: application/json; charset=utf-8
date: Tue, 05 Nov 2019 11:04:10 GMT
后端服务应答
HTTP/1.1 200 OK
Access-Control-Allow-Origin: cloud.baidu.com
Access-Control-Allow-Methods: GET
Date: Tue, 05 Nov 2019 11:04:10 GMT
Content-Type: application/json; charset=UTF-8
Content-Length: 12
{"200","OK"}
API网关应答
HTTP/1.1 200 OK
Access-Control-Allow-Origin: cloud.baidu.com
Access-Control-Allow-Methods: GET
X-Bce-Request-Id: 63c4a3a9-8636-4510-a54f-595fa74069e4
Date: Tue, 05 Nov 2019 11:04:10 GMT
Content-Type: application/json; charset=UTF-8
Content-Length: 12
{"200","OK"}
2.2 实现预先请求模式
浏览器使用 OPTIONS 方法来发送预先请求,以获取服务器对跨域请求的要求信息。
我们可以在 API 网关中配置一个 API,将该 API 的方法设置为 OPTIONS,并且匹配所有子路径。这样就能让预先请求路由到该 API,返回允许跨域请求的响应。
API 的创建过程可参考 API 相关文档,其中需要注意的配置要点有以下几方面。
- 将安全认证方式设置为“无认证”
- 将请求路径设置为“/”
- 勾选“匹配所有子路径”
- 将请求方法设置为“OPTIONS”