同源策略,解决跨域问题方法及原理

前言

Cross-Origin Resource Sharing(CORS) 是W3C为浏览器制定的可以跨域通信的规范. 通过使用 XMLHttpRequest 对象, CORS可以让开发者方便的进行跨域通信, 就像在使用同域通信一样.

CORS的使用十分简单. 想象一下有一个网站 a.com 想要获取另一个网站 b.com 的数据. 但由于浏览器的同源策略, 这样的请求将会被禁止. 这时我们可以使用CORS, 通过添加一些特殊的请求\响应头, 可以让 a.com 访问 b.com 的数据.

通过上面的例子我们可以看出, CORS的支持需要客户端和服务器同时支持才行. 幸运的是, 如果你是一名客户端的开发人员(如前端工程师), 绝大多数的技术细节都会被隐藏掉.

这篇文章将讲述客户端如何发送一个跨域请求, 而服务器又将如何去处理和支持跨域请求.

发送一个跨域请求

时至今日, 发送一个XMLHttprequest请求已经是一个简单的事情, 这里我不在过多赘述.

根据浏览器的同源策略, 当请求的地址与来源地址的协议\域名\端口中的任一值不相同时, 均视为是一个跨域的请求.

XMLHttprequest 的 withCredentials 属性

跨域请求通常不会携带cookies信息. 为了能让跨域请求带上cookies, 你需要将做如下设置:

xhr.withCredentials = true;

为了能让这个属性正常工作, 你还需要在服务器端在响应是带上Access-Control-Allow-Credentials , 同时它的值必须为true. 更多的内容可以看服务器设置的那一部分.

Access-Control-Allow-Credentials: true

设置withCredentials为true后, 在于服务器进行通信时会携带这个域名下的所有cookies, 同时服务器也可以在它的于域名下设置cookies. 但值得注意的是, 这些cookies仍然遵守浏览器的同源策略, 你无法通过javascript访问这个域名下的cookies, 它只被这个域名的服务器控制.

为服务器增加跨域请求的支持

大部分跨域请求的重要操作实在浏览器和服务器之间进行的. 浏览器在跨域请求期间会代表客户端在请求上增加行的请求头, 有的时候还会增加新的请求. 这些操作对于开发者来说是透明, 但是这些请求仍然可以被抓包工具捕获.

跨域请求

浏览器的开发者来实现浏览器端的跨域请求细节, 这节内容来介绍如何配置服务器来支持跨域请求.

跨域请求的分类

通常将跨越请求分为"简单请求"和"非简单请求"两类.

简单请求遵循以下的规则

简单请求

首先它的请求方式只能是GET, POST, PUT

同时的它的请求头部只能包含上面的那几个类型, 值的注意的是Content-Type只能是罗列出的三种(正好是form的entryType的三个值).

对于简单请求, 浏览器可以自行解决其中的跨域问题. 例如我们熟知的一个跨域通信的解决方式JSON-P就是利用GET发送一个简单请求来规避跨域的问题.HTML中的表单提交也不需要处理跨域问题.

任何不符合上述条件的请求都算作非简单请求, 浏览器在处理非简单的跨域请求时会与服务器进行额外的通信(称之为预检请求), 将在下面介绍.

处理简单请求

通过这个cors-demo我们可以方便的查看浏览器与服务器之间的同信.

当浏览器发送一个简单请求时, 我们打开浏览器的Network面板可以看到一个如下的请求(删除部分内容)

GET /get HTTP/1.1
Host: localhost:5051
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 ....
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,en;q=0.8,zh;q=0.6,ja;q=0.4,zh-TW;q=0.2

我们需要注意的一点是, 所有的跨域请求(无论简单或者非简单)总会包含一个Origin的请求头部, 这个属性的值由浏览器添加, 而且不受用户控制. 它的值由协议(如: http), 域名(如: a.om)和端口(只有它不是默认值时才包含, 如80端口)组成, 说明请求的来源.

包含Origin的请求不一定是跨域请求, 但是跨域请求一定包含Origin. 一些同源的请求同样也会包含Origin请求头.例如, Firefox浏览器不会在同源的请求中添加Origin, 但是Chrome和Safari会在同源的POST/PUT/DELETE请求中添加Origin请求头(但是同源的GET不会添加).

浏览器会忽略掉同源请求中的的CORS响应中的设置.

然后我们来看一个有效的跨域请求响应

Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

所有与跨域请求相关的HTTP头部都以Access-Control-开始, 下面是它们的详细信息

  1. Access-Control-Allow-Origin (必选)

    所有有效的跨域响应都必须包含这个请求头, 没有的话会导致跨域请求失败. 它的值可以是请求中的Origin的值, 也可以设置为*来表示可以响应所有来源的请求.

  2. Access-Control-Allow-Credentials (可选)

    默认情况下跨域请求不会携带cookies信息. 如果需要请求携带cookies信息, 则需要将这个值设置为true, 如果不需要就不要设置这个值, 而不是将它设置为false.

    这个请求头需要与 [withCredentials](#XMLHttprequest 的 withCredentials 属性) 配合使用. 只有两个值都设置为true的时候才能够在请求中携带cookies信息. 当withCredentials设置为true, 而响应中不包含Access-Control-Allow-Credentials时, 请求会发生错误.

  3. Access-Control-Expose-Headers (可选)

    XMLHttpRequest2对象上的getResponseHeader()方法可以让你获取到响应中头部信息, 但在跨域请求中,你只能获取到以下信息

    • Cache-Control
    • Content-Language
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma

    如果你希望客户端能过获取其他的头部信息, 可以设置这个值.

处理一个非简单请求

对与开发者来说, 发送一个跨域的非简单请求跟发送一个同域请求没什么区别.但事实上浏览器会发送两个请求, 第一个请求(成为预检请求)会像服务器确定是否接受这个跨域请求, 第二个才是真正的发出请求. 浏览器自动的处理这两个请求, 同时预检请求也是可以被缓存的, 而不用每次请求都需要发送预检请求.

下面是一个预检请求

OPTIONS /cors/post HTTP/1.1
Host: localhost:5051
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 ...
DNT: 1
Referer: http://localhost:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,en;q=0.8,zh;q=0.6,ja;q=0.4,zh-TW;q=0.2

如同简单跨域请求一样, 在预检请求中也包含了Origin请求头, 同时这个请求的方式OPTIONS(所以你必须确定你的服务器能够正常的处理这中请求). 它同时也包含了其他的请求头.

  1. Access-Control-Request-Method

    这个请求头的值就是正式请求的请求方式, 上面的那个例子就是POST

  2. Access-Control-Request-Headers

    它的值是一个由逗号分隔的正式请求中请求头的列表.

预检请求是在实际的请求发出前先向服务器确认是否能够处理这个请求. 服务器应该检查上边两个请求头的值, 来判断这个请求是否有效.

如果服务器确认这个请求有效, 那么它会做出如下的响应

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Content-Type: text/html
Date: Sat, 12 Aug 2017 13:46:08 GMT
Connection: keep-alive
Transfer-Encoding: chunked
  1. Access-Control-Allow-Origin (必选)

    如同简单跨域请求一样, 预检请求的响应也必须包含这个值

  2. Access-Control-Allow-Methods (必选)

    由逗号分隔的HTTP请求方式, 在其中的值表示服务能够接受这中请求方式的跨域请求.

    值得注意的是虽然预检请求只是针对单个请求方式进行检测, 但是你仍可以返回一个你所支持的请求方式列表.这样做的好处是方便对预检请求进行缓存.

  3. Access-Control-Allow-Headers (当预检请求中包含Access-Control-Request-Headers时是必须的)

    由逗号分隔的支持的请求头部列表, 与Access-Control-Allow-Methods类似, 虽然预检请求中只有很少的一部分请求头, 但是你仍然可以返回所有你支持的列表, 原因也是为了缓存.

  4. Access-Control-Allow-Credentials (可选)

    同简单请求

  5. Access-Control-Max-Age (可选)

    在每个请求前面都发送一个预检请求是很浪费资源的, 这个值允许你设置预检请求的缓存时间, 单位是秒.

一旦预检请求通过服务器的检查, 那么浏览器会随后发送实际的请求. 实际请求的处理与简单请求一样.

如果服务器想要拒绝一个跨域请求, 那么他可以直接回复一个简单的响应(如 HTTP 200), 但在响应头中不要包含任何与CORS相关的响应头设置. 服务器也可能会因为预检请求不合法而拒绝这个请求. 如果预检请求中不包含正确的CORS头部设置, 它就不会发送实际的请求.

当跨域请求发生错时, 浏览器会调用onerror事件, 同时会在控制台打印相关的错误信息.

cors

最后附上一个服务器端处理跨域请求的流程图

服务器处理跨域请求流程图

转载至慕课网
链接:https://www.imooc.com/article/19869?block_id=tuijian_wz

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

推荐阅读更多精彩内容