代理认证
由于资源认证和代理认证可以共存,因此需要不同的头和状态码,在代理的情况下,会返回状态码 407(需要代理认证)
, Proxy-Authenticate
响应头包含至少一个适用于代理的情况,Proxy-Authorization
请求头用于将证书提供给代理服务器。下面分别来认识一下这两个标头
Proxy-Authenticate
HTTP Proxy-Authenticate
响应标头定义了身份验证方法,应使用该身份验证方法来访问代理服务器后面的资源。它将请求认证到代理服务器,从而允许它进一步发送请求。例如
Proxy-Authenticate: Basic Proxy-Authenticate: Basic realm="Access to the internal site"
Proxy-Authorization
这个 HTTP 请求
标头和上面的 Proxy-Authenticate
拼接很相似,但是概念不同,这个标头用于向代理服务器提供凭据,例如
Proxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
下面是代理服务器的请求/响应认证过程
这个过程和通用的过程类似,我们就不再详细展开描述了。
禁止访问
如果代理服务器
收到的有效凭据不足以获取对给定资源的访问权限,则服务器应使用403 Forbidden
状态代码进行响应。与 401 Unauthorized
和 407 Proxy Authorization Required
不同,该用户无法进行身份验证。
WWW-Authenticate 和 Proxy-Authenticate 头
WWW-Authenticate
和 Proxy-Authenticate
响应头定义了获得对资源访问权限的身份验证方法。他们需要指定使用哪种身份验证方案,以便希望授权的客户端知道如何提供凭据。它们的一般表示形式如下
WWW-Authenticate: <type> realm=<realm>
Proxy-Authenticate: <type> realm=<realm>
我想你从上面看到这里一定会好奇 <type>
和 realm
是什么东西,现在就来解释下。
<type>
是认证协议,Basic
是下面协议中最普遍使用的
RFC 7617 中定义了
Basic
HTT P身份验证方案,该方案将凭据作为用户ID /密码对传输,并使用 base64 进行编码。(感兴趣的同学可以看看 https://tools.ietf.org/html/rfc7617)
其他的认证协议主要有
认证协议 | 参考来源 |
Basic | 查阅 RFC 7617,base64编码的凭据 |
Bearer | 查阅 RFC 6750,承载令牌来访问受 OAuth 2.0保护的资源 |
Digest | 查阅 RFC 7616,Firefox仅支持md5哈希,请参见错误bug 472823以获得SHA加密支持 |
HOBA | 查阅 RFC 7486 |
Mutual | 查阅 RFC 8120 |
AWS4-HMAC-SHA256 | 查阅 AWS docs |
realm
用于描述保护区或指示保护范围,这可能是诸如 Access to the staging site(访问登陆站点) 或者类似的,这样用户就可以知道他们要访问哪个区域。
Authorization 和 Proxy-Authorization 标头
Authorization 和 Proxy-Authorization 请求标头包含用于通过代理服务器对用户代理进行身份验证的凭据。在此,再次需要类型,其后是凭据,取决于使用哪种身份验证方案,可以对凭据进行编码或加密。一般表示如下
Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
Proxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
HTTP 缓存
通过把请求/响应
缓存起来有助于提升系统的性能,Web 缓存
减少了延迟和网络传输量,因此减少资源获取锁需要的时间。由于链路漫长,网络时延不可控,浏览器使用 HTTP 获取资源的成本较高。所以,非常有必要把数据缓存起来,下次再请求的时候尽可能地复用。当 Web 缓存在其存储中具有请求的资源时,它将拦截该请求并直接返回资源,而不是到达源服务器重新下载并获取。这样做可以实现两个小目标
- 减轻服务器负载
- 提升系统性能
下面我们就一起来探讨一下 HTTP 缓存都有哪些
不同类型的缓存
HTTP 缓存有几种不同的类型,这些可以分为两个主要类别:私有缓存
和 共享缓存
。
- 共享缓存:共享缓存是一种缓存,它可以存储多个用户重复使用的请求/响应。
- 私有缓存:私有缓存也称为
专用缓存
,它只适用于单个用户。 - 不缓存过期资源:所有的请求都会直接到达服务器,由服务器来下载资源并返回。
我们主要探讨
浏览器缓存
和代理缓存
,但真实情况不只有这两种缓存,还有网关缓存,CDN,反向代理缓存和负载平衡器,把它们部署在 Web 服务器上,可以提高网站和 Web 应用程序的可靠性,性能和可伸缩性。
不缓存过期资源
不缓存过期资源即浏览器和代理不会缓存过期资源,客户端发起的请求会直接到达服务器,可以使用 no-cache
标头代表不缓存过期资源。
no-cache 属于 Cache-Control 通用标头,其一般的表示方法如下
Cache-Control: no-cache
也可以使用 max-age = 0
来实现不缓存的效果。
Cache-Control: max-age=0
私有缓存
私有缓存只用来缓存单个用户,你可能在浏览器设置中看到了 缓存
,浏览器缓存包含服务器通过 HTTP 下载下来的所有文档。这个高速缓存用于使访问的文档可以进行前进/后退,保存操作而无需重新发送请求到源服务器。
可以使用 private
来实现私有缓存,这与 public
的用法相反,缓存服务器只对特定的客户端进行缓存,其他客户端发送过来的请求,缓存服务器则不会返回缓存。它的一般表示方法如下
Cache-Control: private
共享缓存
共享缓存是一种用于存储要由多个用户重用的响应缓存。共享缓存一般使用 public
来表示,public
属性只出现在客户端响应中,表示响应可以被任何缓存所缓存。一般表示方法如下
Cache-Control: public
缓存控制
HTTP/1.1 中的 Cache-Control
常规标头字段用于执行缓存控制,使用此标头可通过其提供的各种指令来定义缓存策略。下面我们依次介绍一下这些属性
不缓存
no-store
才是真正意义上的不缓存
,每次服务器接受到客户端的请求后,都会返回最新的资源给客户端。
Cache-Control: no-store
缓存但需要验证
同上面的 不缓存过期资源
私有和共享缓存
同上
缓存过期
缓存中一个很重要的指令就是max-age
,这是资源被视为新鲜
的最长时间 ,与 Expires
相反,此指令是相对于请求时间的。对于应用程序中不会更改的文件,通常可以添加主动缓存。下面是 mag-age 的表示
Cache-Control: max-age=31536000
缓存验证
must-revalidate
表示缓存必须在使用之前验证过时资源的状态,并且不应使用过期的资源。
Cache-Control: must-revalidate
下面是一个缓存验证图
什么是新鲜的数据
一旦资源存储在缓存中,理论上就可以永远被缓存使用。但是不管是浏览器缓存还是代理缓存,其存储空间是有限的,所以缓存会定期进行清除,这个过程叫做 缓存回收(cache eviction)
(自译)。另一方面,服务器上的缓存也会定期进行更新,HTTP 作为应用层的协议,它是一种客户-服务器
模式,HTTP 是无状态的协议,因此当资源发生更改时,服务器无法通知缓存和客户端。因此服务器必须通过某种方式告知客户端缓存已经被更新。服务器会提供过期时间
这个概念,告知客户端在此到期时间之前,资源是新鲜的
,也就是未更改过的。在此到期时间的范围之外,资源已过时。过期算法(Eviction algorithms)
通常会将新资源优先于陈旧资源使用。
这里需要注意一下,过期的资源并不会被回收或忽略,当高速缓存接收到过期资源时,它会使用 If-None-Match
转发此请求,以检查它是否仍然有效。如果有效,服务器会返回 304 Not Modified
响应头并且没有任何响应体,从而节省了一些带宽。
下面是使用共享缓存代理的过程
这个图应该比较好理解,只说一下 Age 的作用,Age 是 HTTP 响应标头告诉客户端源服务器在多久之前创建了响应,它的单位为秒
,Age 标头通常接近于0,如果是0则可能是从源服务器获取的,如果不是表示可能是由代理服务器创建,那么 Age 的值表示的是缓存后的响应再次发起认证到认证完成的时间值。
缓存的有效性是由多个标头来共同决定的,而并非某一个标头来决定。如果指定了 Cache-control:max-age=N
,那么缓存会保存 N 秒。如果这个通用标头不存在的话,则会检查是否存在 Expires
标头。如果 Exprires 标头存在,那么它的值减去 Date 标头的值就可以确定其有效性。最后,如果max-age
和 expires
都不存在,就去寻找 Last-Modified
标头,如果存在此标头,则高速缓存的有效性等于 Date 标头的值减去 Last-modified 标头的值除以10。
缓存验证
当到达缓存资源的有效期时,将对其进行验证或再次获取。仅当服务器提供了强验证器
或弱验证器
时,才可以进行验证。
当用户按下重新加载按钮时,将触发重新验证。如果缓存的响应包含 Cache-control:must-revalidate
标头,则在正常浏览下也会触发该事件。另一个因素是 高级 -> 缓存首选项 面板中的缓存验证首选项。有一个选项可在每次加载文档时强制进行验证。
Etag
我们上面提到了强验证器和弱验证器,实现验证器功能的标头正式 Etag 的作用,这意味着 HTTP 用户代理(例如浏览器)不知道该字符串表示什么,并且无法预测其值。如果 Etag 标头是资源响应的一部分,则客户端可以在未来请求的标头中发出 If-None-Match
,以验证缓存的资源。
Last-Modified
响应标头可以用作弱验证器,因为它只有1秒可以分辨的时间。如果响应中存在 Last-Modified
标头,则客户端可以发出 If-Modified-Since
请求标头来验证缓存资源。(关于 Etag 更多我们会在条件请求介绍)
避免碰撞
通过使用 Etag 和 If-Match 标头,你可以检测避免碰撞。
例如,在编辑 MDN 时,将对当前 Wiki 内容进行哈希处理并将其放入响应中的 Etag 中
Etag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
当将更改保存到 Wiki 页面(发布数据)时,POST 请求将包含 If-Match 标头,其中包含 Etag 值以检查有效性。
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
如果哈希值不匹配,则表示文档已在中间进行了编辑,并返回 412 Precondition Failed
错误。
缓存未占用资源
Etag 标头的另一个典型用法是缓存未更改的资源,如果用户再次访问给定的 URL(已设置Etag),并且该 URL过时,则客户端将在 If-None-Match 标头字段中发送其 Etag 的值
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
服务器将客户端的 Etag(通过 If-None-Match 发送)与 Etag 进行比较,以获取其当前资源版本,如果两个值都匹配(即资源未更改),则服务器会发回 304 Not Modified
状态,没有主体,它告诉客户端响应的缓存仍然可以使用。
HTTP CROS 跨域
CROS 的全称是 Cross-Origin Resource Sharing(CROS)
,中文译为 跨域资源共享
,它是一种机制。是一种什么机制呢?它是一种让运行在一个域(origin)
上的 Web 应用被准许访问来自不同源服务器上指定资源的机制。在搞懂这个机制前,你需要线了解什么是 域(origin)
Origin
Web 概念中域(Origin)
的内容由scheme(protocol) - 协议
,host(domain) - 主机
和用于访问它的 URL port - 端口
定义。仅仅当 scheme 、host、port 都匹配时,两个对象才有相同的来源。这种协议相同,域名相同,端口相同的安全策略也被称为 同源策略(Same Origin Policy)
。某些操作仅限于具有相同来源的内容,可以使用 CORS 取消此限制。
跨域的特点
- 下面是跨域问题的例子,看看你是否清楚什么是跨域了
上面这两个 URL 是否具有跨域问题呢?
上面两个 URL 是不具有跨域问题的,因为这两个 URL 具有相同的协议(scheme)
和主机(host)
- 那么下面这两个是否具有跨域问题呢?
这两个 URL 也不具有跨域问题,为什么不具有,端口不一样啊。其实它们两个端口是一样的。
或许你会认为这两个 URL 是不一样的,放心,关于一样不一样的论据我给你抛出来了
协议和域名部分是不区分大小写的,但是路径部分则根据服务器平台而定。Windows 和 Mac OS X 系统是不区分大小写的,而采用UNIX和Linux系的服务器系统是区分大小写的,
也就是说上面的 Example.com
和 example.com
其实是一个网址,并且由于两个地址具有相同的 scheme 和 host ,默认情况下服务器通过端口80传递 HTTP 内容,所以上面这两个地址也是相同的。
- 下面这两个 URL 地址是否具有跨域问题?
这两个 URL 的 scheme 不同,所以这两个 URL 具有跨域问题
- 再看下面这三个 URL 是否具有跨域问题
这三个 URL 也是具有跨域问题的,因为它们隶属于不通服务器的主机 host。
- 下面这两个 URL 是否具有跨域问题
这两个 URL 也是具有跨域问题,因为这两个 URL 的默认端口不一样。
</div>