跨域 -- cors

  1. Origin 、 Host 、 Referer
    1.1 Origin —简单请求
GET  HTTP/2
Origin: https://h5test.selfshero.com
Referrer Policy: strict-origin-when-cross-origin

● 组成:协议 + 域名 + 端口
● 浏览器发现这次跨域 AJAX 请求是简单请求,就自动在头信息之中,添加一个Origin字段
● 用Origin字段用来说明 来自哪里 (跨域),请求来自于哪个站点,服务器根据这个值,决定是否同意这次请求。
● 它用于Cors请求和同域 Post请求,仅包含站点信息,不包含任何路径信息
● 如果Origin指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是200。
● 如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

○ Access-Control-Allow-Origin
■ 该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
○ Access-Control-Allow-Credentials
■ 该字段可选。它的值是一个布尔值,表示是否允许发送 Cookie。默认情况下,Cookie 不包括在 CORS 请求之中。设为true,即表示服务器明确许可,浏览器可以把 Cookie 包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送 Cookie,不发送该字段即可。
○ Access-Control-Expose-Headers
■ 该字段可选。CORS 请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个服务器返回的基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。
使用 Vary: Origin 让同一个 URL 有多份缓存,比如常见的Vary: Accept-Encoding表示客户端要根据Accept-Encoding请求头的不同而使用不同的缓存,比如 gizp 的缓存一份,未压缩的缓存为另一份。
withCredentials 属性
上面说到,CORS 请求默认不包含 Cookie 信息(以及 HTTP 认证信息等),这是为了降低 CSRF 攻击的风险。但是某些场合,服务器可能需要拿到 Cookie,这时需要服务器显式指定Access-Control-Allow-Credentials字段,告诉浏览器可以发送 Cookie
Access-Control-Allow-Credentials: true
同时,开发者必须在 AJAX 请求中打开withCredentials属性
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
否则,即使服务器要求发送 Cookie,浏览器也不会发送。或者,服务器要求设置 Cookie,浏览器也不会处理。
但是,有的浏览器默认将withCredentials属性设为true。这导致如果省略withCredentials设置,这些浏览器可能还是会一起发送 Cookie。这时,可以显式关闭withCredentials。 xhr.withCredentials = false
需要注意的是,如果服务器要求浏览器发送 Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨域)原网页代码中的document.cookie也无法读取服务器域名下的 Cookie。下面看一个简单的案例

var xhr = new XMLHttpRequest();
var url = 'http://127.0.0.1/js/request.php?';
xhr.onload = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            console.info(xhr.responseText);
        } else {
            console.error(xhr.statusText);
        }
    }
};

xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(data);

当我们以localhost的名义去访问服务器时,http的请求头会加上Orgin字段

1.2 Host
Host 请求头指明了服务器的域名(对于虚拟主机来说),以及(可选的)服务器监听的TCP端口号。
如果没有给定端口号,会自动使用被请求服务的默认端口(比如请求一个HTTP的URL会自动使用80端口)。
HTTP/1.1 的所有请求报文中必须包含一个Host头字段。如果一个 HTTP/1.1 请求缺少 Host 头字段或者设置了超过一个的 Host 头字段,一个400(Bad Request)状态码会被返回。
作用:同一台机器上。可能部署多个服务,通过解析host+端口,指定具体访问的站点
1.2.1 host要点
○ 去哪里, 描述请求将被发送的目的地,值为客户端将要访问的远程主机
○ 浏览器在发送http请求时会带有此Header
○ http/1.0不带host头,http/1.1新增host头
○ HTTP2 对应字段为 :authority,主要用于服务器区分服务。
○ host可以是域名,可以是IP地址,host字段域名/ip后可以跟端口号
○ host可以由程序自定义,某些程序为了防止运营商或者绕过防火墙,可以定义虚假host。
○ http/1.0的host可以为空但不可以不带,如果不带,会返回400 Bad request
○ http请求头包含host字段、响应头不包含
○ 部分站点不验证host,可以任意传值
1.2.2 重点解析 :用来实现虚拟主机技术
虚拟主机(virtual hosting)即共享主机(shared web hosting),可以利用虚拟技术把一台完整的服务器分成若干个主机,因此可以在单一主机上运行多个网站或服务。
host字段表示目标服务器域名。我们知道一般服务器都只会有一个ip地址,而一个ip地址可以有多个域名(站点),比如我们有www.baidu.com,www.taobao.com和www.jd.com几个域名,在域名供应商那通过A记录或者CNAME方式记录与服务器的ip地址关联,那么通过任何一个域名访问最终解析到的都是该ip。
举个栗子,有一台 ip 地址为 61.135.169.125 的服务器,在这台服务器上部署着百度、淘宝,京东的网站。为什么我们访问“www.baidu.com”时,看到的是 百度 的首页而不是淘宝或京东的首页?原因就是 Host 请求头决定着访问哪个虚拟主机。如果服务器后台解析出Host但是服务器上找不到相应的站点,那么这个连接很可能会被丢弃,从而报错。

综上所述,个人理解host字段是代表你的请求将要到哪台(虚拟)主机,并会在服务端被验证,如果不符合,就不能正确处理客户端的请求。
1.3 Referer
着看一下MDN对referer的介绍:
Referer 首部包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer 首部识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。
1.3.1作用

  1. 统计访问流量等
    例如从我主页上链接到一个朋友那里,他的服务器就能够从HTTP Referer中统计出每天有多少用户点击我主页上的链接访问他的网站
  2. 防盗链
    什么是防盗链?
    防盗链是下载时判断来源地址是不是在网站域名之内, 否则就不能下载或显示。
    比如在一个网页里面插入一个超链接,链接到其他的网页,那么当点击这个超链接从而链接到另外一个页面的时候,相当于浏览器向 web 服务器发送了一个 http 请求,对于另外一个页面而言,这个 referer 就是上一个页面的 URL,而对于从地址栏里面直接输入 URL 或者是刷新网页的方式,则 referer = null,通过设置这个 referer 可以防止盗链的问题。
  3. 安全验证,防止恶意请求
    当然,对于某些恶意用户,也可能伪造Referer来获得某些权限,
    还可用做电子商务网站的安全,在提交信用卡等重要信息的页面用referer来判断上一页是不是自己的网站,如果不是,可能是黑客用自己写的一个表单,来提交,为了能跳过你上一页里的javascript的验证等目的。
    所以注意不要把Rerferer用在身份验证或者其他非常重要的检查上,因为Rerferer非常容易在客户端被改变
    虽然Referer并不可靠,但用来防止图片盗链还是足够的,毕竟不是每个人都会修改客户端的配置。实现一般都是通过apache的配置文件,首先设置允许访问的地址:
    1.3.2 referer不会被发送情况
    一般情况下浏览器会带有此Header,但下面情况不会带有Referer这个头,
  4. 页面来源采用的协议为本地文件的’file’或’data’URI
  5. 当前请求页面采用的是非安全协议(http),而来源页面采用的是安全协议 (https )
  6. 直接输入网址或者通过浏览器书签访问
  7. 使用js的location.href()或者是location.replace()
  8. 使用iframe的hack写法去除referer,在这里举一个防图片盗链的例子
<meta name="referrer" content="never">
<meta http-equiv="expires" content="2626560">//设置浏览器缓存,建议大于一天
//expires的值既可以为具体的秒数,也可以为特定的时间,这个时间必须为GMT时间
  1. 使用html5中的noreferrer
<a href="/test/index.php?noreferer" rel="noreferrer" target="_blank">noreferrer</a> 

1.4 Host、Referer与Origin对比
需要使用这三者的场景:
● 处理跨域请求时,必须判断来源请求方是否合法;
● 后台做重定向的时候,需要原地址信息
区别
● Host 描述请求将被发送的目的地,包括,且仅仅包括域名和端口号。 在任何类型请求中,request都会包含此header信息。
● Origin 用来说明请求从哪里发起的,包括,且仅仅包括协议和域名。 这个参数一般只存在于CORS跨域请求中,可以看到response有对应的header:Access-Control-Allow-Origin。
● Referer 告知服务器请求的原始资源的URI,其用于所有类型的请求,并且包括:协议+域名+查询参数(注意,不包含锚点信息)。因为原始的URI中的查询参数可能包含ID或密码等敏感信息,如果写入referer,则可能导致信息泄露。
2. 浏览器的同源策略
浏览器的职责是展示/渲染document、css、script脚本等,但是这些资源(将document、css、script统一称为资源)可能来自不同的地方,如本地、远程服务器、甚至黑客的服务器…浏览器作为万维网的入口,是我们接入互联网最重要的软件之一(甚至没有之一),因此它的安全性显得尤为重要,这就出现了浏览器的同源策略。
同源策略是浏览器一个重要的安全策略,它用于限制一个origin源的document或者它加载的脚本如何能与另一个origin源的资源进行交互。它能帮助阻隔恶意文档,减少(并不是杜绝)可能被攻击的媒介。
2.1 同源的定义
URL被称作:统一资源定位符,同源是针对URL而言的,一个完整的URL如图所示

Tips:域名和host是等同的概念,域名+端口号 = host+端口号(大部分情况下你看到域名并没有端口号,那是采用了默认端口号80而已)
● 同源:只和协议和域名有关,全部相同则为同源
○ 两部分必须完全一样才算同源
○ 域名包含端口号,所以总共是两部分而非三部分
■ 协议+host+port

下面通过举例来彻底了解下。譬如,我的源URL为:http://www.baidu.com/api/user,下面表格描述了不同URL的各类情况:

                    URL	                      是否同源	                   原因说明

http://www.baidu.com/account 是 前两部分相同,path路径不一样而已
https://www.baidu.com/api/user 否 协议不同
http://www.baidu.com:8080/api/user 否 端口不同
http://map.baidu.com/api/user 否 域名不同
2.2 不同源的网络访问
浏览器同源策略的存在,限制了不同源之间的交互,实为不便。但是浏览器也开了一些“绿灯”,让其不受同源策略的约束。此种情况一般可分为如下三类:

  1. 跨域写操作(Cross-Origin writes):一般是允许的,如链接(a标签)、重定向以及表单提交(如form表单的提交)
  2. 跨域资源嵌入(Cross-origin embedding):一般是是允许的,比如下面的例子
  3. 跨域读操作(Cross-origin reads):一半不被允许,比如http接口请求属于次范畴
    简单一句话总结:浏览器自己是可以发起跨域请求的(比如a标签、img标签、form表单等),但是js是不能去跨域获取资源(ajax)
    2.3 如何允许不同源的网络访问
    上面说到的第三种情况:跨域读操作一般是不允许跨域访问的,而这种情况是我们开发过程中最关心、最常见的情况,因此必须解决。
    Tips:这里的读指的是广义上的读,指的是从服务器获取资源(有response)的都叫读操作,而和具体是什么Http Method无关。换句话讲,所有的Http API接口请求都在这里都指的是读操作
    可以使用 CORS 来允许跨源访问。CORS 是 HTTP 的一部分,它允许服务端来指定哪些主机可以从这个服务端加载资源。
  4. 什么是cors跨域
    Cors(Cross-origin resource sharing):跨域资源共享,它是浏览器的一个技术规范,由W3C规定,规范的wiki地址在此:https://www.w3.org/wiki/CORS_Enabled#What_is_CORS_about.3F
    它是浏览器的一种(自我保护)行为,并且已形成规范。也就是说:后端请求后端是不存在此现象的
    若想实现Cors机制的跨域请求,是需要浏览器和服务器同时支持的。关于浏览器对CORS的支持情况:可以认为100%的浏览器都是支持的,再加上CORS的整个过程都由浏览器自动完成,前端无需做任何设置,所以前端工程师的ajax原来怎么用现在还是怎么用,它对前端开发人员是完全透明的。
    3.1 为何需要Cors跨域访问
    浏览器费尽心思的搞个同源策略来保护我们的安全,但为何又需要跨域来打破这种安全策略呢?其实啊,这一切都和互联网的快速发展有关~
    随着Web开放的程度越来越高,页面的内容也是越来越丰富。因此页面上出现的元素也就越来越多:图片、视频、各种文字内容等。为了分而治之,一个页面的内容可能来自不同地方,也就是不同的domain域,因此通过API跨域访问成了必然。
    浏览器作为进入Internet最大的入口,很长时间它是个大互联公司的必争之地,因此市面上并存的浏览器种类繁多且鱼龙混扎:IE 7、8、9、10,Chrome、Safari、火狐,每个浏览器对跨域的实现可能都不一样。因此对开发者而言亟待需要一个规范的、统一方案,它就是Cors。
    CORS(Cross-Origin Resource Sharing)由W3C组织于2009-03-17编写工作草案,直到2014-01-16才正式毕业成为行业规范,所有浏览器得以遵守。至此,程序员同学们在解决跨域问题上,只需按照Cors规范实施即可。
    3.2 Cors的工作原理
    Web资源涉及到两个角色:浏览器(消费者)和服务器(提供者),面向这两个角色来了解Cors的原理非常简单,如下图所示:

a. 若浏览器发送的是个跨域请求,http请求中就会携带一个名为Origin的头表明自己的“位置”,如Origin: http://localhost:5432
b. 服务端接到请求后,就可以根据传过来的Origin头做逻辑,决定是否要将资源共享给这个源。而这个决定通过响应头Access-Control-Allow-Origin来承载,它的value值可以是任意值,有如下情况:
ⅰ. 无此头:不共享给此origin
ⅱ. 有此头:值有如下可能情况
1. 值为*,通配符,允许所有的Origin共享此资源
2. 值为http://localhost:5432(也就是和Origin相同),共享给此Origin
3. 值为非http://localhost:5432(也就是和Origin不相同),不共享给此Origin
c. 浏览器接收到Response响应后,会去提取Access-Control-Allow-Origin这个头。然后根据上述规则来决定要接收此响应内容还是拒绝。
Tips:Access-Control-Allow-Origin响应头只能有1个,且value值就是个字符串。另外,value值即使写为http://aa.com,http://bb.com这种也属于一个而非两个值
3.3 Cors细粒度控制:授权响应头
在Cors规范中,除了可以通过Access-Control-Allow-Origin响应头来对主体资源(URL级别)进行授权外,还提供了针对于具体响应头更细粒度的控制,这个响应头就是:Access-Control-Expose-Headers。换句话讲,该头用于规定哪些响应头(们)可以暴露给前端,默认情况下这6个响应头无需特别的显示指定就支持:
● Cache-Control
● Content-Language
● Content-Type
● Expires
● Last-Modified
● Pragma
若不在此值里面的头将不会返回给前端(其实返回了,只是浏览器让其对前端不可见了而已,对JavaScript也不可见哦)。
但是,但是,但是,这种细粒度控制header的机制对简单请求是无效的,只针对于非简单请求(也叫复杂请求)。由此可见,将哪些类型的跨域资源请求划分为简单请求的范畴就显得特备重要了。
3.4 何为简单请求
Cors规范定义简单请求的原则是:请求不是以更新(添加、修改和删除)资源为目的,服务端对请求的处理不会导致自身维护资源的改变。对于简单跨域资源请求来说,浏览器将两个步骤(取得授权和获取资源)合二为一,由于不涉及到资源的改变,所以不会带来任何副作用。
对于一个请求,必须同时符合如下要求才被划为简单请求:
a. Http Method只能为其一:
ⅰ. GET
ⅱ. POST
ⅲ. HEAD
b. 请求头只能在如下范围:
ⅰ. Accept
ⅱ. Accept-Language
ⅲ. Content-Language
ⅳ. Content-Type,其中它的值必须如下其一:
1. application/x-www-form-urlencoded
2. multipart/form-data
3. text/plain
除此之外的请求都为非简单请求(也可称为复杂请求)。
非简单请求是那种对服务器提出特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求可能对服务端资源改变,因此Cors规定浏览器在发出此类请求之前必须有一个“预检(Preflight)”机制,这也就是我们熟悉的OPTIONS请求。
3.5 什么是Preflight预检机制
顾名思义,它表示在浏览器发出真正请求之前,先发送一个预检请求,这个在Http里就是OPTIONS请求方式。这个请求很特殊,它不包含主体(无请求参数、请求体等),主要就是将一些凭证、授权相关的辅助信息放在请求头里交给服务器去做决策。因此它除了携带Origin请求头外,还会额外携带如下两个请求头:
● Access-Control-Request-Method:真正请求的方法
● Access-Control-Request-Headers:真正请求的自定义请求头(若没有自定义的就是空呗)
服务端在接收到此类请求后,就可以根据其值做逻辑决策啦。如果允许预检请求通过,返回个200即可,否则返回400或者403。
如果预检成功,在响应里应该包含上文提到的响应头Access-Control-Allow-Origin和Access-Control-Expose-Headers,除此之外,服务端还可以做更精细化的控制,这些精细化控制的响应头为:
● Access-Control-Allow-Methods:允许实际请求的Http方法(们)
● Access-Control-Allow-Headers:允许实际请求的请求头(们)
● Access-Control-Max-Age:允许浏览器缓存此结果多久,单位:秒。有了缓存,以后就不用每次请求都发送预检请求啦
说明:以上响应头并不是必须的。若没有此响应头,代表接受所有

预检请求完成后,有个关键点,便是浏览器拿到预检请求的响应后的处理逻辑,这里描述如下:

  1. 先通过自己的Origin匹配预检响应中的Access-Control-Allow-Origin的值,若不匹配就结束请求,若匹配就继续下一步验证
  2. 拿到预检响应中的Access-Control-Allow-Methods头。若此头不存在,则进行下一步,若存在则校验预检请求头Access-Control-Request-Method的值是否在此列表中,在其内继续下一步,否则失败
  3. 拿到预检响应中的Access-Control-Request-Headers头。同请求头中的Access-Control-Allow-Headers值记性比较,全部包含在内则匹配成功,否则失败

以上全部匹配成功,就代表预检成功,可以开始发送正式请求了。值得一提的事,Access-Control-Max-Age控制预检结果的浏览器缓存,若缓存还生效的话,是不用单独再发送OPTIONS请求的,匹配成功直接发送目标真实即可。

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

3.6 Access-Control-Max-Age使用细节
Access-Control-Max-Age用于控制浏览器缓存预检请求结果的时间,这里存在一些使用细节你需要注意:

  1. 若浏览器禁用了缓存,也就是勾选了Disable cache,那么此属性无效。也就说每次都还得发送OPTIONS请求
  2. 判断此缓存结果的因素有两个:
    a. 必须是同一URL(也就是Origin相同才会去找对应的缓存)
    b. header变化了,也会重新去发OPTIONS请求(当然若去掉一些header编程简单请求了,就另当别论了)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值