同源策略(CORS)探究
一、有哪些场景
- 由
XMLHttpRequest
或者Fetch
发起跨域 HTTP 请求 - Web FONT (CSS @font-face)
- WebGL 贴图
- canvas API 中的 drawImage
- CSS 中的资源请求
二、简单的 CORS 实现
在跨域 AJAX 请求中, 浏览器端无需做任何操作,会自动在 header 信息中添加一个 Orgin
字段;
如果是 Fetch ,则需要添加字段 mode: 'cors'
。
1 | GET http://square.10jqka.com.cn/usercenter/strategy/getrecommendlist HTTP/1.1 |
这是我截的 Request Headers,第 7 行的 Origin
是 XMLHttpRequest 自动加在请求头中的 ,表示请求来自哪个站点,与 Referer
首部字段相似。
而对应的,服务端需要在响应头中加入字段 Access-Control-Allow-Origin
, 表示该资源可以允许哪些外域访问。
1 | HTTP/1.1 200 OK |
如上面例子的第 7 行, Access-Control-Allow-Origin: *
表示允许任何外域访问该资源。
如果请求响应头没有包含 Access-Control-Allow-Origin
,则 XMLHttpRequest
会捕获错误,走入 onerror
回调中。
至此,一个最简单的 CORS 就可以了。
三、携带 cookie
CORS 请求默认是不发送 Cookie 和 HTTP 认证信息的,如果需要携带 cookie, 浏览器端和服务端都需要再做一些操作。
3.1 浏览器
浏览器端 XHR 需要打开 withCredentials
参数,Fetch 则需要设置 credentials
参数,如下
1 | // Ajax |
1 | // Fetch |
这里需要注意下,有的内核实现的 XMLHttpRequest 是默认开启 withCredentials 的。
3.2 服务端
加上
Access-Control-Allow-Credentials: true
, 表示服务器允许认证信息携带Access-Control-Allow-Origin
不可以是星号(*),必须指明具体的域名
1 | Access-Control-Allow-Credentials: true |
四、预检请求 Preflight Request
- POST 请求变成 OPTIONS 了怎么办?
- 给跨域请求写了自定义的 header 就报错了怎么回事?
也许某天你要经历或经历过这样的问题,那是因为你触发了 Preflight request
,至于具体触发预检的情况,往下翻。
4.1 解决方案
服务端需要再在 header 中加入一些字段,响应 OPTIONS 请求来表明是否预检通过。
1 | Access-Control-Allow-Credentials: true |
(1) Access-Control-Allow-Methods
该字段为必需,表示在 preflight request 中所要访问资源允许使用的方法或方法列表
(2) Access-Control-Allow-Headers
如果在浏览器端发起 XMLHttpRequest 时写了非规范的 header 字段, 那么 Access-Control-Allow-Headers
是必须的。
表示在 preflight request 中所要访问资源允许携带的 header 字段
(3) Access-Control-Max-Age
可选这段,指定了 preflight request 的结果可以被缓存多久
这个字段从一些第三方资料看,可能存在一些bug,某些浏览器的旧版本可能有上限值;另外禁用 cache 的情况下也会无效。
4.2 触发条件
当满足下面任意一项条件,就会先发出预检请求
使用了下面任一 HTTP 方法:
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
请求头中有安全首部字段集合之外的字段
- Accept
- Content-Type
- 等…
具体查官方文档,各浏览器可能有一定差异
Content-Type
的值不是下述之一- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
请求中的
XMLHttpRequestUpload
对象注册了任意多个事件监听器请求中使用了
ReadableStream
对象
五、为什么设计预检请求
Why is there a preflight request?
For most type of requests two resource sharing checks are performed. Initially a “permission to make the request” check is done on the response to the preflight request. And then a “permission to read” check is done on the response to the actual request. Both of these checks need to succeed in order for success to be relayed to the API (e.g. XMLHttpRequest). The “permission to make the request” check is performed because deployed servers do not expect such cross-origin requests. E.g., a request using the HTTP DELETE method. If they reply positively to the preflight request the client knows it can go ahead and perform the actual desired request.
摘自wiki-CORS,大致意思是一是认为对于大多数请求进行两次请求是可行的,二是进行预检校验是很多场景安全性上的考虑。
或者可以这么说,预检请求本身其实并没有做什么安全性上的检查,只是这项新的特性必须不影响各个项目,项目方必须知道有这样一项检查并针对它做些设置。
六、与 JSONP 比较?
- JSONP 不存在兼容问题,而 CORS 你必须确认你需要兼容的设备,必要情况需要做降级处理
- JSONP 只支持 GET 请求, CORS 则能支持所有 HTTP 请求类型
- JSONP 单次请求, CORS 触发预检则会有两次请求