CORS解决跨域问题

2020-03-25  本文已影响0人  张云飞Vir

0. 背景

浏览器中,网站A的网络请求访问网站A的资源(图片,HTTP请求)是很顺畅的,而想访问网站B的资源,就要面对跨域资源访问的问题了。面对跨域问题,有很多的解决方案,本文讨论使用 CORS 来解决的方案。

本文结构

1. 什么是跨域问题,什么是同源策略
  1.1 不同源则触发一个跨域的HTTP请求
  1.2 同源策略
  1.3 源
2. CORS 概述
3. CORS 的控制场景
  3.1 简单请求
  3.2 预检请求
  3.3 附带携带身份凭据的请求
  3.4 响应头的额外暴露字段
  3.5 预检请求的缓存时长

1. 什么是跨域问题,什么是同源策略

跨域资源共享是由同源策略引发的,首先要了解同源策略。而要了解同源策略先要了解什么是“源”,下面我们层层展开。

1.1 不同源则触发一个跨域的HTTP请求:

在浏览器中,当 “一个资源” 向 “与它所在的服务器不同的域、协议或端口” 请求一个资源时,该资源会发起一个跨域 HTTP 请求。

浏览器可能“限制发起跨域请求",或者是 “可以发起跨域请求,但是返回结果被浏览器拦截”。

出于安全原因,浏览器限制跨源HTTP请求。这意味着使用 Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。

1.2 同源策略

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

也就是说,如果“源”相同,则运行访问。如果不同,则被限制。我们继续了解下什么是源。

1.3 源

Web内容的源由它的URL的 协议,主机(域名)和端口定义。

只有当协议,主机和端口都匹配时,两个对象被认为具有相同的起源。而可以使用 CORS 解除这个限制。

源由三部分组成:

同源的例子

网址 说明
http://example.com/app2/index.htmlhttp://example.com/app1/index.html 同源,以为都是http和域名相同
http://Example.com:80http://example.com 同源,虽然写80端口,单实际上80是默认端口(可以省略)

不同源的例子

网址 说明
http://example.com/app1https://example.com/app2 不同源,因为不同的协议: http 对比 https
http://example.comhttp://www.example.comhttp://myapp.example.com 不同源,因为不同的主机名
http://example.comhttp://example.com:8080 不同源,因为不同的端口号。

浏览器的同源策略提升了安全性,然而在业务需求中仍然需要需要“访问不同源的资源”,于是提出了“CORS机制”。

现代浏览器支持使用 CORS,以降低跨域 HTTP 请求所带来的风险。CORS 机制允许 Web应用 进行跨域访问控制,从而使跨域数据传输得以安全进行。

2. CORS 概述

跨域资源共享 CORS 是一种机制,它使用额外的 HTTP头 来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的资源。

CORS 使用额外的请求头来说明访问是被允许的

跨域资源请求分为:

跨域资源共享标准新增了一组 HTTP 请求头字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。

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

CORS请求失败会产生错误,但是为了安全,在JavaScript代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。

3. CORS 的控制场景

下面分几个场景来说明。

3.1 简单请求

简单请求不会触发 CORS 预检请求。若请求满足所有下述条件,则该请求可视为“简单请求”:

使用下列方法之一:
    GET
    HEAD
    POST
HTTP的头信息不超出以下几种字段:
    Accept
    Accept-Language
    Content-Language
    Content-Type 的值仅限于下列三者之一:
      text/plain
      multipart/form-data
      application/x-www-form-urlencoded

交互流程

image.png

(1)请求端:
当发起一个跨域请求时,浏览器会自动在请求头中加入 Origin 字段,它是发起方所处于的域,表明了“来源”。

示例:请求中含有
Origin: http://foo.example

(2)服务端:
服务端根据“来源” 来决定处理方式,如果同意,则返回的消息头中添加 Access-Control-Allow-Origin 字段。这个字段的值可以是“ * ”(表示任意的域名都允许),或者是具体的域名地址。

  Access-Control-Allow-Origin: *

简单请求的跨域,通过 Access-Control-Allow-Origin 请求头的处理。 如果在 请求头中 包含了特殊自定义内容,就需要 预检请求 了。

3.2 预检请求(preflight request)

“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。

"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

当请求满足下述任一条件时,即应首先发送预检请求:

(1)使用了下面任一 HTTP 方法:
    PUT
    DELETE
    CONNECT
    OPTIONS
    TRACE
    PATCH
 (2)Content-Type 的值不属于下列之一:
        application/x-www-form-urlencoded
        multipart/form-data
        text/plain
 (3)请求头中包含的自定义请求头
    比如含有 Authorization, token 作为授权的字段

交互流程

image.png

示例假设:
假设我们自定义了一个 请求头字段 “X-PINGOTHER” , 后续将在请求中携带这个请求头字段。

(1) 请求端:
先发一个 OPTION 的预检请求,内容有:

Origin 说明了来源
Access-Control-Request-Method 说明 下次将正式采用的方法。Access-Control-Request-Headers 说明将采用的自定义header 字段名。

示例:

Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

(2) 服务端:
服务收到上面的请求后,根据自身情况来判定是否接收和处理。如果同意接受,则返回的 响应中包含下面几个请求头。

Access-Control-Allow-Origin 说明了支持跨域的来源
Access-Control-Allow-Methods 说明了支持的跨域方法
Access-Control-Allow-Headers 说明了 将接受的自定义header字段名
Access-Control-Max-Age说明了 预检请求的结果能够被缓存多久,即在多久内可以省略 预检请求 。

  Access-Control-Allow-Origin: http://foo.example
  Access-Control-Allow-Methods: POST, GET, OPTIONS
  Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
  Access-Control-Max-Age: 86400

至此,完成了 预检。

(3) 请求端
预检请求完成之后,发送实际请求,在这里 假设的自定义请求头字段 X-PINGOTHER ,就会被放在请求头中了。示例:

X-PINGOTHER: pingpong
Origin: http://foo.example

(4) 服务端:
服务端 根据实际情况处理请求,仍然要返回 Access-Control-Allow-Origin 声明。

Access-Control-Allow-Origin: http://foo.example

是否需要发送 预检请求,是浏览器根据规则自动做出判断。预检的过程和头部字段也是浏览器自动处理。如果在这个过程中发生了“拒绝”,那么,在发送预检请求后,就没后后续了,浏览器会 “不再发送实际的请求”,或者 “丢失实际请求中的响应”。

3.3 附带携带身份凭据的请求

对于跨域 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。

(1)请求端
在请求端中的 withCredentials 属性则告诉浏览器“ 是否自动在请求中携带 cookie 的值 ”

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

(2)服务端
服务端的请求头中的 Access-Control-Allow-Credentials 说明了是否接受凭据信息(比如cookie)。

Access-Control-Allow-Credentials: true

如果 请求端包含了 withCredentials ,而服务端未包含 Access-Control-Allow-Credentials,那么浏览器将丢失 这次 服务端的响应内容,而不传递给请求的发送者。

附带身份凭证的请求与通配符
对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“”。
这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“
”,请求将会失败

3.4 响应头的额外暴露字段

服务端通过响应头中的字段 Access-Control-Expose-Headers 来说明额外暴露字段。

CORS请求时,一般只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。

如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

3.5 预检请求的缓存时长

Access-Control-Max-Age 头指定了预检请求的结果能够被缓存多久

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

参数 delta-seconds 表示 预检请求的结果在多少秒内有效。

END

上一篇下一篇

猜你喜欢

热点阅读