内容简介:CORS存在感非常低,但它又存在于几乎所有的WEB页面中。CORS 全称是”跨域资源共享”(CORS的诞生源于浏览器的
CORS存在感非常低,但它又存在于几乎所有的WEB页面中。
CORS是什么?
CORS 全称是”跨域资源共享”( Cross-origin resource sharing )。是W3C定义的一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个源 (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器 不同的域 、 协议或端口 请求一个资源时,资源会发起一个 跨域 HTTP 请求 。
为什么会有CORS?
CORS的诞生源于浏览器的 同源安全策略 。所谓的同源,即 协议相同 、 域相同 、 端口号相同 。基于同源策略,浏览器会对脚本内发起的跨源HTTP请求(如XMLHttpRequest和Fetch API )进行控制,如果不满足同源策略请求会被限制或者拦截返回结果。这意味着使用这些API的Web应用只能请求同域的HTTP资源,除非响应报文包含了正确CORS响应头。
在严格的同源策略下,为了兼顾跨域请求CORS诞生了。跨域资源共享( CORS )机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以降低跨域 HTTP 请求所带来的风险。
什么情况下需要 CORS ?
跨域资源共享标准允许在下列场景中使用跨域 HTTP 请求:
- 由 XMLHttpRequest 或 Fetch 发起的跨域 HTTP 请求。
- Web 字体 (CSS 中通过 @font -face 使用跨域字体资源)
- WebGL 贴图
- 使用 drawImage 将 Images/video 画面绘制到 canvas
- 样式表(使用 CSSOM )
CORS如何管理来自外部资源的请求?
跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
以下是CORS标准添加的新HTTP标头:
- Access-Control-Allow-Origin
- Access-Control-Allow-Credentials
- Access-Control-Allow-Headers
- Access-Control-Allow-Methods
- Access-Control-Expose-Headers
- Access-Control-Max-Age
- Access-Control-Request-Headers
- Access-Control-Request-Method
- Origin
CORS请求访问控制场景示例
接下来通过几个示例来解释跨域资源共享机制的工作原理。
简单请求和非简单请求
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。浏览器对这两种请求的处理,是不一样的。
若请求满足所有下述条件,则该请求可视为 简单请求 :
-
使用下列方法之一:
- GET
- HEAD
- POST
-
Fetch 规范定义了 对 CORS 安全的首部字段集合 ,不得人为设置该集合之外的其他首部字段。该集合为:
- Accept
- Accept-Language
- Content-Language
- Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
- 请求中没有使用 ReadableStream 对象。
不满足上述条件的视为 非简单请求 。
简单请求
对于简单请求,浏览器只需要通过请求首部的 Origin 和响应首部的 Access-Control-Allow-Origin 就能完成跨域权限的控制。
如下例子:
请求报文和响应报文如下:
// request header GET /test HTTP/1.1 ... Referer: https://liayal.com/test.html Origin: https://liayal.com .... // response header HTTP/1.1 200 OK ... Server: nginx/1.12.2 Access-Control-Allow-Origin: * Content-Type: application/xml ...
请求首部中的 Origin 表示当前请求的源,上面的例子则表明该请求来源于https://liayal.com。
响应首部的 Access-Control-Allow-Origin 则指定了允许访问该资源的外域URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。示例中的中 Access-Control-Allow-Origin 返回为 *
表示允许所有外域访问(一般不会这么配置)。
如果 Access-Control-Allow-Origin
指定了具体的域名而非 *
(如:https://liayal.com )那么除了指定的域名外,其它外域均不能访问该资源。 Access-Control-Allow-Origin
应当为 * 或者包含由 Origin 首部字段所指明的域名。
如果Origin指定的源,不在许可范围内,服务器会返回一个不包含Access-Control-Allow-Origin字段的响应,浏览器会抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。但是 这种错误无法通过状态码识别 ,因为HTTP回应的状态码有可能是200。
非简单请求
非简单请求再发送实际请求之前会增加一次请求方法为 OPTIONS 的 预检请求 ,以获知服务器是否允许该实际请求。
浏览器通过预检请求 获取服务器允许的请求方法和请求头,以及当前域是否在服务器的许可名单之内 ,只有得到肯定答复,浏览器才会发出正式的Http请求,否则就报错。预检请求的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
查看以下代码:
cosnt xhr = new XMLHttpRequest(); const url = 'https://api.liayal.com/test/'; const body = '{"a": 1}'; xhr.open('POST', url, true); xhr.setRequestHeader('X-Custom-Header', 'test'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(body);
上面的代码通过POST请求向服务端发送了一个json,请求中包含了一个自定义的请求首部字段(X-Custom-Header: test),同时Content-Type 为 application/json。根据上文的条件,这是一个非简单请求需要发送预检请求。
// request header OPTIONS /test/ HTTP/1.1 ... Origin: https://www.liayal.com Access-Control-Request-Method: POST Access-Control-Request-Headers: X-Custom-Header, Content-Type ... // response header HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://www.liayal.com/ Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-Custom-Header, Content-Type Access-Control-Max-Age: 86400 Vary: Accept-Encoding, Origin ...
上面预检请求的请求报文中,增加了两个请求首部字段:
Access-Control-Request-Method: POST Access-Control-Request-Headers: X-Custom-Header, Content-Type
首部字段 Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法, 而Access-Control-Request-Headers 则告知服务器,实际请求将携带两个自定义请求首部字段:X-Custom-Header 与 Content-Type。服务器据此决定,该实际请求是否被允许。
接下来看预检请求的响应部分,接下来的实际请求能否继续取决于下面几个响应首部:
Access-Control-Allow-Origin: https://www.liayal.com/
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-Custom-Header, Content-Type
首部字段 Access-Control-Allow-Methods 表明服务器允许客户端使用 POST, GET 和 OPTIONS 方法发起请求。
首部字段 Access-Control-Allow-Headers 表明服务器允许请求中携带字段 X-PINGOTHER 与 Content-Type。
首部字段 Access-Control-Max-Age 表明该响应的有效时间为 86400 秒,也就是 24 小时。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。
上述CORS响应首部只有在服务器判定允许跨域请求后返回,如果预检请求不通过,会返回一个正常的HTTP响应,但是没有任何CORS头部信息。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。
XMLHttpRequest cannot load https://api.liayal.com. Originhttps://www.liayal.com is not allowed by Access-Control-Allow-Origin.
预检请求通过后,接下来就可以发送实际请求了,
// request header POST /test/ HTTP/1.1 ... X-Custom-Header: test Content-Type: application/json; charset=UTF-8 Referer: https://www.liayal.com/test Origin: https://www.liayal.com ... "{"a": 1}" // response header HTTP/1.1 200 OK Access-Control-Allow-Origin: http://foo.example Vary: Accept-Encoding, Origin Content-Type: text/plain ...
上述请求过程中Access-Control-Allow-Origin字段每次响应中必定包含。
附带身份凭证的跨域请求
一般情况下,对于跨域请求,浏览器不会发送身份凭证信息。如果要发送身份凭证信息,需要手动设置相关参数。
如下,我们从 https://www.liayal.com/test 向 https://api.liayal.com 发起一个 get 请求。
cosnt xhr = new XMLHttpRequest(); const url = 'https://api.liayal.com/test'; xhr.open('GET', url, true); xhr.withCredentials = true; xhr.send();
上面的代码中,我们将 XMLHttpRequest 的 withCredentials 设置为 true,在请求发起时会向服务器发送认证信息(Cookie)。需要注意的是,如果服务器端的响应中未携带 Access-Control-Allow-Credentials: true ,浏览器将不会把响应内容返回给请求的发送者。
请求报文:
// request header GET /test HTTP/1.1 Referer: https://www.liayal.com/test Origin: https://www.liayal.com Cookie: token=jlfkafadsadf ... // response header HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://www.liayal.com Access-Control-Allow-Credentials: true Vary: Accept-Encoding, Origin Content-Type: text/plain ...
对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为 *
。
希望本文能让你对跨域请求有一个清晰的认识,有问题欢迎留言。(完)
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Web 开发需要知道的 2017 WWDC
- Android开发需要了解的 IM 知识
- Android开发需要知道的Binder解析
- 前端为什么需要模块化开发
- Java开发环境不再需要配置classpath
- 微信小程序开发需要注意的一些规范
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。