跨域资源共享(CORS)

166 阅读8分钟

CORS是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的"预检"请求。

在下列场景中使用跨站点 HTTP 请求:

  • 由 XMLHttpRequest或 Fetch APIs发起的跨源 HTTP 请求。
  • Web 字体 (CSS 中通过 @font-face 使用跨源字体资源),因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。
  • WebGL 贴图
  • 使用drawImage将 Images/video 画面绘制到 canvas。
  • 来自图像的 CSS 图形 (en-US)

规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST请求),浏览器必须首先使用 OPTIONS方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies和 HTTP 认证相关数据)。

出于安全性,浏览器限制脚本内发起的跨源HTTP请求。例如,XMLHttpRequest  和 Fetch API 遵循同源策略。这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头

简单请求 vs 复杂请求

简单请求不需要先发起options请求,而复杂请求则需要先发起预检请求。

针对简单请求,在进行CORS设置的时候,服务端只需要设置

Access-Control-Allow-Origin:*
// 如果只是针对某一个请求源进行设置的话,可以设置为具体的值
Access-Control-Allow-Origin: 'http://www.yourwebsite.com'

针对复杂请求,我们需要设置不同的响应头。因为在预检请求的时候会携带相应的请求头信息

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-CUSTOMER-HEADER, Content-Type

相应的响应头信息为:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
// 设置max age,浏览器端会进行缓存。没有过期之前真对同一个请求只会发送一次预检请求
Access-Control-Max-Age: 86400

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

  • 使用下列方法之一:get,head,post
  • 除了以下的请求头字段之外,没有自定义的请求头
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type的值只有以下三种
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded

(Content-Type一般是指在post请求中,get请求中设置没有实际意义)

  • 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器 

    • XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问
  • 请求中没有使用 ReadableStream 对象 

如果发送的预检请求被进行了重定向,那大多数的浏览器都不支持对预检请求的重定向。我们可以通过先发送一个简单请求的方式,获取到重定向的url,然后再去请求这个url。

附带身份凭证的请求

XMLHttpRequest 或 Fetch与 CORS 的一个有趣的特性是,可以基于 HTTP cookies和 HTTP 认证信息发送身份凭证。一般而言,对于跨源 XMLHttpRequest或 Fetch请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置XMLHttpRequest的某个特殊标志位

如果在发送请求的时候,给xhr 设置了withCredentials为true,从而向服务器发送 Cookies,如果服务端需要向客户端也发送cookie的情况,需要服务器端也返回Access-Control-Allow-Credentials: true响应头信息,否则浏览器不会把响应内容返回给请求的发起者。

预检请求和凭据

CORS 预检请求不能包含凭据。预检请求的响应必须指定 Access-Control-Allow-Credentials: true 来表明可以携带凭据进行实际的请求。 在响应附带身份凭证的请求时:

  • 服务器不能将 Access-Control-Allow-Origin 的值设为通配符“*”,而应将其设置为特定的域,如:Access-Control-Allow-Origin: https://example.com
  • 服务器不能将 Access-Control-Allow-Headers 的值设为通配符“*”,而应将其设置为首部名称的列表,如:Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
  • 服务器不能将 Access-Control-Allow-Methods 的值设为通配符“*”,而应将其设置为特定请求方法名称的列表,如:Access-Control-Allow-Methods: POST, GET

对于附带身份凭证的请求(通常是 Cookie),服务器不得设置 Access-Control-Allow-Origin 的值为“*”。这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“*”,请求将会失败。而将 Access-Control-Allow-Origin 的值设置为 https://example.com,则请求将成功执行。 另外,响应首部中也携带了 Set-Cookie 字段,尝试对 Cookie 进行修改。如果操作失败,将会抛出异常。

跨域的响应头

Access-Control-Allow-Origin响应首部中可以携带一个 Access-Control-Allow-Origin字段,其语法如下:

Access-Control-Allow-Origin: <origin> | *

其中,origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。如果服务端指定了具体的域名而非“*”,那么响应首部中的 Vary字段的值必须包含 Origin。这将告诉客户端:服务器对不同的源站返回不同的内容。

Access-Control-Expose-Headers在跨源访问时,XMLHttpRequest 对象的 getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。Access-Control-Expose-Headers 头让服务器把允许浏览器访问的头放入白名单。

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

这样浏览器就能够通过 getResponseHeader 访问 X-My-Custom-Header 和 X-Another-Custom-Header 响应头了。

Access-Control-Max-Age头指定了 preflight 请求的结果能够被缓存多久。delta-seconds 参数表示 preflight 预检请求的结果在多少秒内有效。

Access-Control-Max-Age: <delta-seconds>

Access-Control-Allow-Credentials头指定了当浏览器的 credentials 设置为 true 时是否允许浏览器读取 response 的内容。当用在对 preflight 预检测请求的响应中时,它指定了实际的请求是否可以使用 credentials,对于简单请求,如果响应报文不包含该字段,则该响应将被忽略。

Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。

Access-Control-Allow-Headers首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。

跨域HTTP请求头部字段

Origin首部字段表明预检请求或实际请求的源站

Access-Control-Request-Method首部字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。

Access-Control-Request-Headers首部字段用于预检请求。其作用是,将实际请求所携带的首部字段告诉服务器。

跨域解决方案

  1. CORS,通过响应头设置Access-Control-Allow-Origin允许跨域
  2. 原生的Jsonp:通过动态创建script标签,通过script标签的src,向一个不同源的接口发送一个get请求,其原理是src属性发送请求时,在参数中额外携带一个callback的参数,参数值是一个在页面中预先定于好的函数名。callback属性值:预先定义的函数名,这个函数必须要在script标签之前定义 服务器接收到请求之后,获取callback的参数值。服务器将要响应的数据拼接成函数调用格式,通过传参的方式将响应数据返回给浏览器。

Jsonp只支持get请求,不支持post请求

通过<script> <img> <iframe> <link>标签的跨域请求不受同源策略的约束。

通过<script><img>标签实现跨域请求的区别

<img>标签只能通过监听onload和onerror事件知道什么时候接收到了响应进而进行后续处理,但是无法获取数据;

  1. 主域相同,document.domain + iframe
  2. postMessage
  3. loaction.hash + iframe
  4. window.name + iframe

参考连接: segmentfault.com/a/119000002…