每一个WEB开发都需要知道的CORS

栏目: 后端 · 前端 · 发布时间: 5年前

内容简介: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标头:

CORS请求访问控制场景示例

接下来通过几个示例来解释跨域资源共享机制的工作原理。

简单请求和非简单请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。浏览器对这两种请求的处理,是不一样的。

若请求满足所有下述条件,则该请求可视为 简单请求

  1. 使用下列方法之一:

    • GET
    • HEAD
    • POST
  2. 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/testhttps://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 的值为 *

希望本文能让你对跨域请求有一个清晰的认识,有问题欢迎留言。(完)


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

设计之下

设计之下

搜狐新闻客户端UED团队 / 电子工业出版社 / 2014-1-1 / CNY 69.00

形而上者谓之道,形而下者谓之器。匠者,器也。处身平凡的匠人不断追求向上的设计之道。本书没有华丽的辞藻和长篇大论的理论,作者是搜狐一线的设计团队,写作过程中他们尽力还原真实的工作场景,并总结出了一些实用的经验和方法。 《设计之下》共三部分,全面讲解了用户体验设计的流程和方法。第一部分为“交互设计”,阐述了从项目启动、解析需求到原型设计的过程,并且总结了交互设计的要点:大局观、操作流程简捷、形式......一起来看看 《设计之下》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具