前言
对于跨域,随着w3c的CORS的出现,相比较于有些年头的jsonp,CORS以其简单安全,支持post的优势越来越收到大家的欢迎。具体如何CORS的原理和实现,直接推荐阮老师的文章,十分详细。本文主要关注CORS实现过程中的几个疑惑点。
预检请求
背景
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
简单请求
同时满足一下条件的即是简单请求:
- 请求方法是以下三种方法之一:
HEAD、GET、POST - HTTP的头信息不超出以下几种字段
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form、multipart/form-data、text/plain非简单请求
显然,不同时满足则为非简单请求(可以认为是复杂请求)。两者的差别在于复杂请求在与服务端交互时多了一次options的预检请求,毕竟复杂请求一般就是HTTP请求头信息超出限制或者method为put、delete等操作行为,处于安全考虑,需要服务端先行验证来决定是否给予相关权限。
如下所示(示例来自阮老师文章):
1 | var url = 'http://api.alice.com/cors'; |
对于预检信息,服务端一般做了如下操作:
1、检查origin、Access-Control-Request-Method和Access-Control-Request-Headers等字段,确认是否允许跨域,如果允许跨域作出回应:
1 | HTTP/1.1 200 OK |
如果不允许跨域,依然响应该请求,不过不携带CORS相关的信息。浏览器则会认为服务器不允许跨域,触发错误。
1 | // 常见的跨域错误 |
到这里一个流程结束,不过我们要关注的是options 预检请求之后 code 返回的问题
options 成功之后,返回code 200 还是 204
常规预检的就是对于options的请求直接返回code 200的响应,表示校验通过。
但是前两天发现有的返回为code204。两者之间的差别具体在哪呢。
常见用法
1、针对特定接口支持CORS时,在代码里加判断对于options返回200
1 | // 随便找了段java代码 |
2、如果整个域名都支持CORS,可以再nginx侧直接配置,此时常见的是返回204.
1 | if ($request_method = 'OPTIONS') { |
总结
两者之间的差别,首先可以参考下204和200 对应的含义(下面内容摘自MDN)。
200
请求成功,成功的具体含义依据http method 的不同而有所差别。:
- GET: 资源已经被提取并在消息中文中传递
- POST: 描述动作结果的资源在消息体中传输
204
服务器成功处理了请求,但不需要返回任何实体内容,并且希望返回更新了的元信息。
客户端是浏览器的haul,用户浏览器应保留发送了该请求的页面,而不产生任何文档视图上的变化。由于204响应被禁止包含任何消息体,因此它始终以消息头后的第一个空行结尾。
简单总结,204返回表示请求成功,并且无消息体,优势在于节省网络请求。
具体到options请求,选用哪一个。
贴切的来说,应该像其他options请求一样为预检optiosn请求返回相同的code状态码,相关规范不要求或者推荐其他内容。
fecth请求
例如对于Fetch 规范 要求CORS协议的status可以为200-209里面的任意值。
1 | If a CORS check for request and response returns success |
如果response为一个okstatus就可以继续执行
1 | An ok status is any status in the range 200 to 299, inclusive. |
并不要求具体哪一个值。
所以从fetch来看,两者均可选择。
HTTP 1.1
对于http/1.1 规范来说,有一章节专门定义了各种响应code。对于2开头的2-XXcode,分别描述如下:
- 200
请求成功,成功的具体含义依据http method 的不同而有所差别。 - GET: 资源已经被提取并在消息中文中传递
- POST: 描述动作结果的资源在消息体中传输
- OPTIONS: communications options成功的表示
由上可知,对于options预检请求的响应,需要包含下面两种情况:
1、表明请求成功
2、描述通信选项(这里包括, Access-Control-Allow-Methods 和 Access-Control-Allow-Headers这些响应头)
看起来,上面就是200在http定义中的含义,显然满足,但是如果继续看204的含义,好像也可以满足需求。
204
服务器成功处理了请求,但不需要返回任何实体内容,并且希望返回更新了的元信息。
客户端是浏览器的话,用户浏览器应保留发送了该请求的页面,而不产生任何文档视图上的变化。由于204响应被禁止包含任何消息体,因此它始终以消息头后的第一个空行结尾。
结论
首先两者都可以使用,对于200,从定义而言更符合场景和定义。但是204无消息体,优势在于节省网络请求。
至于用哪个,大家自行做下判断。
跨域 读取cookie
作为常见的场景,cookie一般会存放一些,鉴权会话等信息。对于CORS跨域,默认的是不包含cookie的。
1 | A cross-origin request by default does not bring any credentials (cookies or HTTP authentication) |
如果要操作cookie需要分别从服务端和客户端两个场景来看。
客户端 request 携带cookie
request如果要携带cookie,需要特定参数指明。可能看到过这个参数为credentials或者withCredentials,什么时候用两者呢。主要跟请求的实现有关:
Fetch 使用credentials
直接使用原生Fetch的话,需要设置credentials。credentials 是Request接口的只读属性,用于表示用户代理是否应该在跨域请求的情况下从其他域发送cookies。这与XHR的withCredentials 标志相似,不同的是有三个可选值(后者是两个):
- omit: 从不发送cookies.
- same-origin: 只有当URL与响应脚本同源才发送 cookies、 HTTP Basic authentication 等验证信息.(浏览器默认值,在旧版本浏览器,例如safari 11依旧是omit,safari 12已更改)
- include: 不论是不是跨域的请求,总是发送请求资源域在本地的 cookies、 HTTP Basic authentication 等验证信息.
CORS跨域的时候,只需要如下设置:
1 | fetch('http://another.com', { |
- XHR 使用withCredentials
基于XMLHttpRequest实现的请求使用withCredentials来允许携带cookie。
该属性为boolean类型,所以只有true/false两个取值,默认为false。
这样也很好理解,默认不携带是处于安全考虑。
使用如下
1 | var xhr = new XMLHttpRequest(); |
适用框架:jquery的ajax,axios等。
服务端 Access-Control-Allow-Credentials
当客户端设置了允许携带cookie之后,并不能完成该操作,毕竟是跨域,服务端也需要做响应设置,否则浏览器拿不到正确响应。
1 | Access-Control-Allow-Credentials:true |
看MDN 的解释:
1 |
|
当 credentials为include的时候,通知浏览器是否将响应暴露给前端jscode,如果为false,js不能读取响应自然请求报错。
只有Access-Control-Allow-Credentials为true时,才会将响应暴露给客户端。
当作为预检请求响应头时,表明该实际请求(即后面的真正请求)是否可以使用credentials。
不过对于简单请求,因为没有预检,如果服务端没有正确响应,浏览器会忽略该属性,并不会直接报错。
需要与XMLHttpRequest.withCredentials属性或者Fetch 的credentials 配合使用。
注意
如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。
同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传。
且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。
毕竟cookie是有path来保证封闭性的,如果可以随便读取不管从安全还是性能上都是一种隐患。
多域名跨域
对于多域名跨域,方法比较多。
1、Access-Control-Allow-Origin:*
允许任意域名跨域,显然支持多域名。不过从安全性和cookie的使用的角度来看并不推荐。
2、动态匹配域名
这种实现方式比较多,原理就是声明允许的多域名配置,可以是数组或者是正则,根据当前请求的域名,来判断是否在适用返回内,在的话则设置Access-Control-Allow-Origin为当前域名。
具体实现这里就不写了。
结束语
参考文章
http://www.ruanyifeng.com/blog/2016/04/cors.html
https://fetch.spec.whatwg.org/#cors-protocol-and-credentials
http://www.yunweipai.com/archives/9381.html
以上是在工作中偶然发现的几点疑惑,解决之后深究了下具体原理。希望能对其他同学有所帮助,抛砖引玉,一起努力。