CORS解决跨域问题

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 使用额外的请求头来说明访问是被允许的

跨域资源请求分为:

  • (1)服务器通过请求头来声明“允许的源站,和允许的资源”
  • (2)预检请求
  • (3)携带身份凭据(cookie等)的情形

跨域资源共享标准新增了一组 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,030评论 5 464
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,198评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,995评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,973评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,869评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,766评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,967评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,599评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,886评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,901评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,728评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,504评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,967评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,128评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,445评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,018评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,224评论 2 339