8

认证方式

常见的认证方式可分为4种:

  • BASIC 认证(基本认证)
  • DIGEST 认证(摘要认证)
  • SSL 客户端认证
  • FormBase 认证(基于表单认证)

BASIC 认证

BASIC 认证(基本认证)是从 HTTP/1.0 就定义的认证方式。
BASIC 认证虽然采用 Base64 编码方式,但这不是加密处理,因此安全性不高,而且没有注销操作的设计,因此并不常用。

步骤

  1. 客户端发送请求(未带认证信息);
  2. 服务器返回 401,告知需要验证。响应头部举例:
    WWW-Authenticate: Basic realm="Input Your ID and Password."
  3. 客户端发送的字符串内容是由用户 ID 和密码构成,两者中间以冒号(:)连接后,再经过 Base64 编码处理。 请求头部举例:
    Authorization: Basic Z3Vlc3Q6Z3Vlc3Q=
    当用户代理为浏览器时,用户仅需输入用户 ID 和密码即可,浏览器会自动完成到 Base64 编码的转换工作。
  4. 服务器读取 Authorization 字段,验证通过返回 200,否则重复②。

示意图如下:(来自MDN)
HTTPAuth.png

实现

// 基于 express 实现 BASIC 认证
// 学习 express :https://github.com/expressjs/express/
// 将代码复制进一个 express 项目的路由部分,跑起来,用浏览器访问即可体验。
// 用户名:Yee,密码:123456

router.get('/more/login', function (req, res) {
    const auth = req.headers.authorization
    if (auth) {
        const [type, credentials] = auth.split(' ')
        const [username, password] = atob(credentials).split(':')
        if (type === 'Basic' && username === 'Yee' && password === '123456') {
            res.end('Authorization Succeeded')
            return
        }
    }
    res.setHeader('WWW-Authenticate', 'Basic realm="Input Your ID and Password."')
    res.status(401)
    res.end('Authorization Required')
})

DIGEST 认证

为弥补 BASIC 认证存在的弱点,从 HTTP/1.1 起就有了 DIGEST 认证。
DIGEST 认证同样使用质询 / 响应的方式(challenge/response),但不会像 BASIC 认证那样直接发送明文密码。 过程类似于 BASIC 认证,示意图参考上面的。
虽然相比 BASIC 认证安全性有所提高,但还是没有注销操作的设计,也无法验证防止用户伪装,因此使用有限。

步骤

  1. 客户端发送请求(未带认证信息);
  2. 服务器返回 401,告知需要验证,响应头部举例:
    WWW-Authenticate: Digest realm="DIGEST", nonce="MOSQZ0itBAA=44abb6784cc9cbfc605a5b0893d36f23de 95fcff", algorithm=MD5, qop="auth"
    首部字段 WWW-Authenticate 内必须包含 realm 和 nonce 这两个字段的信息,其他均为可选参数。客户端就是依靠向服务器回送这两个 值进行认证的。
  3. 客户端回传②中的所有信息,并加上 username、url、response 字段,response 就是经过用户名密码等信息加密而已的字符串;请求头部举例:
    Authorization: Digest username="guest", realm="DIGEST", nonce="MOSQZ0itBAA=44abb6784cc9cbfc605a5b0893d36f23de95f cff", uri="/digest/", algorithm=MD5, response="df56389ba3f7c52e9d7551115d67472f", qop=auth, nc=00000001, cnonce="082c875dcb2ca740"
  4. 服务器使用相同的加密方法得到一个字符串,将它与 response 比较,相同就通过返回 200,否则重复②。

SSL 客户端认证

借由 HTTPS 的客户端证书完成认证的方式。凭借客户端证书认证,服务器可确认访问是否来自已登录的客户端。具体过程会在 HTTPS 篇中讲到。
在多数情况下,SSL 客户端认证不会仅依靠证书完成认证,一般会和基于表单认证组合形成一种双因素认证(Two-factor authentication)来使用。 第一个认证因素的 SSL 客户端证书用来认证客户端计算机,另一个认证因素的密码则用来确定这是用户本人的行为。。

基于表单的认证

由于使用上的便利性及安全性问题,HTTP 协议标准提供的 BASIC 认证和 DIGEST 认证几乎不怎么使用。因此实际 Web 开发中,还是由开发者自行实现一套认证机制,安全性高低自然由开发者自己控制。
虽然基于表单的认证方式并不是在 HTTP 协议中定义,是由 Web 应用程序各自实现,但业内还是产生了一些通用解决方案,比如 session-cookie 机制(这部分将在“会话跟踪技术”中详讲)。

会话跟踪技术

cookie

cookie 实际上是服务器保存在客户端上的一小段的文本信息。以键值对的形式保存,并由客户端维护其有效期。服务器通过响应报文头 set-cookie 进行设置(种)。当客户端再次请求该源时,会在请求报文头里将有效的 cookie 提交给服务器。
cookie 遵循同源策略。cookie 这种保存并自动回传一定数据的特性,使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。

关于什么是同源策略,可以参考本人另一篇文章:
传送门

响应头 set-cookie

形如:

set-cookie: <key>=<value>; Expires=<date>; Secure; HttpOnly

key 为属性名,value 为值,一个 set-cookie 设置一个 key,如需设置多个 key,只需要同时返回多个 set-cookie,例子中 Expires、Secure、HttpOnly 为可选值。所有可选属性如下:

属性名说明文字
Expires超时时间点,默认是 Session,即关闭浏览器时失效。(注1)
Max-Age失效前的秒数。优先级高于 Expires。
Domain可以使用这个 cookie 的域,二级域名可指定为一级,一级只能指定为一级。
Path可以使用这个 cookie 的路径,默认为文档所在的文件目录,父级路径可用于所有子级(即设置为根路径“/”就可用于该域名的所有路径)。
Secure限制该 cookie 只通过 HTTPS 传递。
HttpOnly限制该 cookie 只由服务器读写,不能被 js 获取到。
SameSiteChrome51 开始支持,可对跨域的 cookie 进行限制。优先级高于 CORS 的设置,不论是 src、form 发起的请求,还是 ajax 请求。(注2)

注1:现代浏览器多开选项卡和窗口都不会影响 Session 周期,即超时时间设置为 Session 的 cookie 会一直有效,除非彻底退出浏览器应用。 (即使关掉所有窗口,但应用还在后台也不影响)
注2:SameSite可以指定为Strict、Lax、None三个值,默认为Lax:

  • Strict 为最严格模式,将会完全禁止第三方 cookie,即不可能在跨域的情况下携带 cookie,不论 Access-Control-Allow-Credentials 是否设置;
  • Lax 只允许链接、预加载、GET 表单三种情况发送 cookie;
  • None 为无限制,但必须启用 Secure 属性,即只在 HTTPS 的环境下才发送 cookie。

注3:哪些情况下cookie会被认为是第三方的?

源(发起请求页面的域名)cookie 域是否被认为第三方
publisher.compublisher.com
publisher.comdemo.com
demo.com(通过publisher.com页面的iframe加载)demo.com

请求头 cookie

形如:

cookie: <key>=<value>; <key>=<value>...

key 为属性名,value 为值,会一次返回该源下的所有有效 key,以分号为分割。这个结果与在浏览器执行 document.cookie 获取到的值相同(无HttpOnly的时)。

session

session 是服务器记录客户状态的机制。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。客户端再次访问时只需要从该 session 中查找该用户的状态。

session-cookie 机制

一般 session 与 cookie 配合使用,构成会话跟踪技术,即 session-cookie 机制。
服务器生成 session 的 id(图上叫 sessionid)后,就将它通过 set-cookie 传递到客户端,客户端保存这个 sessionid,下次请求通过 cookie 回传到服务器,服务器即可通过 sessionid 查询到用户的 session,进而获得用户状态。

示意图:
session-cookie

如何理解“登录状态过期”?

我们日常使用中经常会遇到登录状态过期的情况,这一般就是 session-cookie 机制引起的。它可分为三种情况的原因:

  1. 保存在客户端的 cookie 过期或被移除了;
  2. 另一种是保存在服务器的 session 过期或被移除;
  3. 两边的登录信息不一致。

连接管理模型

在 HTTP/1.x 里有多种模型:短连接,长连接,和 HTTP 管线化(pipelining)。
HTTP 管线化实用性不高,并没有浏览器支持,已经被 HTTP/2.0 的多路复用特性所取代,这里就不详细讨论了。
连接的长与短只是相对的概念,并没有严格意义上规定多久才算长。讨论 HTTP 的长连接与短连接,本质是讨论 TCP 连接的复用情况。
连接模型由请求报文头和响应报文头里的 Connection 字段决定,值为 close 则为短连接,值为 Keep-Alive 则为长连接(这里与 TCP 里的 keepalive 是不同的概念)。HTTP/1.0 默认是短连接,HTTP/1.1 默认是长连接。只有服务器与客户端协商一直才会进行长连接,并且双方在任意时刻都可以关闭,彼此都不受影响。

扩展阅读:
TCP 的 KeepAlive 机制意图在于保活、心跳,检测连接错误。当一个 TCP 连接两端长时间没有数据传输时(通常默认配置是2小时),发送 keepalive 探针,探测链接是否存活。

短连接(Multiple Connection)

也叫多重连接,客户端和服务器之间进行 HTTP 操作,每次建立一次 TCP 连接,结束就断开连接,下个请求要重新建立 TCP 连接。
基础篇(传送门)中讲到HTTP协议在 1.0 版本之前的一个特点是“无连接”,这个也就是短连接,只是在后续版本种变成了可选项。

示意图:

短连接

长连接(Persistent Connection)

也叫持续连接,客户端和服务器之间进行 HTTP 操作,只建立一次 TCP 连接,多次资源请求都复用该 TCP 连接,完成后再关闭。
当请求报文头有 Connection: Keep-Alive 就会告知服务器客户端支持长连接,服务器如果也支持长连接,则会返回带有 Keep-Alive 字段的响应报文头。
下面例子中,服务器告知客户端,连接超时时间为 10 秒,也就是 10 秒内必须有下一个请求,否则 TCP 就会关闭;然后最多维持 500 秒,也就是说 500 秒后会强制关闭 TCP。

Connection: Keep-Alive

=> 服务器也支持长连接 =>

Keep-Alive: timeout=10, max=500
Connection: Keep-Alive

示意图:
长连接

缺陷及变通手段

长连接虽然增加了 TCP 连接的复用率,但实质上还是基于请求/响应模式的,并不能实现及时更新、或是服务器主动向客户端发送信息。于是就有了“ajax 轮询”、“ajax 长轮询(Long Poll)”这两种变通手段。

  • ajax轮询:原理很简单,就是每隔一段时间就发起一次请求,已到达及时更新的目的。
  • ajax长轮询:服务器接收到请求后如果没有更新内容就不响应,客户端也不做超时处理一直处于等待状态,一直等到有更新内容,服务器才响应,客户端接收到响应后又再发起请求,如此往复。

这两种变通手段都是以消耗大量服务器资源为代价,因此为了根本解决这个问题,后来发展出了 WebSocket 协议,这就是后话了。

与 HTTP 协作的 Web 服务

Web 代理(Web Proxy)

代理扮演的是“中间人”角色,对于连接到它的客户端来说,它是服务端;对于要连接的服务端来说,它是客户端。它就负责在两端之间来回传送 HTTP 报文。代理可能不止一层。
HTTP 客户端向代理发送请求报文,代理服务器需要正确地处理请求和连接(例如正确处理 Connection: keep-alive);转发时,需要附加 Via 首部字段以标记出经过的主机信息;同时向服务器发送请求,并将收到的响应转发给客户端。

示意图:
Web 代理

作用

利用缓存技术减少网络带宽的流量,组织内部针对特定网站的访问控制,以获取访问日志为主要目的,等等。

缓存代理

最广泛的应用就是 CDN 网络。代理转发响应时,会预先将资源的副本(缓存)保存在代理服务器上。当代理再次接收到对相同资源的请求时,就可以不从源服务器那里获取资源,而是将之前缓存的资源作为响应返回。反之, 则叫“非缓存代理”。

透明代理

转发请求或响应时,不对报文做任何加工的代理类型被称为透明代理。反之,对报文内容进行加工的代理被称为“非透明代理”。

网关(Gateway)

网关的工作机制和代理十分相似。 他们的区别:代理是转发相同协议的数据,网关是转换不同协议的数据。
其实大部分的 API 服务器都可以视为网关,因为它需要连接 redis,mysql 等其他服务器以满足业务需求。

示意图:
网关

隧道(Tunnel)

隧道的目的是确保客户端能与服务器进行安全的通信,但它对于双方都是透明的。隧道本身不会去解析 HTTP 请求。也就是说,请求保持原样(可能中间有转码和解码的过程)中转给之后的服务器。隧道会在通信双方断开连接时结束。
HTTPS 算是最常见的应用,我们为了访问外网使用的“那种工具”,部分也属于这类。

示意图:
隧道

内容协商机制

指客户端和服务器就响应的资源内容进行交涉,然后提供给客户端最为合适的资源。内容协商会以响应资源的语言,字符集,编码方式等作为判断的基准。HTTP 中主要以“服务器驱动”的协商方式进行。

协商方式及优缺点

客户端驱动

客户端发起请求,服务器发送可选项列表,客户端作出选择后再发送第二次请求。

  • 优点:容易实现,给用户选择权。
  • 缺点:增加访问次数和延迟。

服务器驱动

服务器检查客户端的请求头部集并决定提供哪个版本的页面。(现在最普遍)

  • 优点:比较快,没有额外开销,支持优先级匹配。
  • 缺点:头部集都不匹配的时候,服务器只能猜测。

透明协商

某个中间设备(通常是缓存代理)代表客户端进行协商。

  • 优点:比较快。
  • 缺点:需要中间设备,非 HTTP 标准。

服务器驱动内容协商

客户端通过请求报文头传递告诉服务器支持情况,并且可以带有近似匹配的优先级,服务器回应的响应报文头里进行确认,就完成了内容协商。

相关请求报文头

字段名说明文字
Accept告诉服务器自己能接受的媒体类型
Accept-Language能接受的语言
Accept-Charset能接受的字符集(如 unicode)
Accept-Encoding能接受的编码方式(如 utf-8)

相关响应报文头

字段名说明文字
Content-Type对应 Accept
Content-Language对应 Accept-Language
Content-Type对应 Accept-Charset
Content-Encoding对应 Accept-Encoding

近似匹配(优先级)

比如:

Accept-Encoding:en;q=0.5,fr;q=0.0,nl;q=1.0,tr;q=0.0

这里nl的优先级是1.0,最高优先级,所以会优先返回。

断点续传和多线程

通过在报文头里两个参数实现的,客户端发请求时对应的是 Range,服务器响应时对应的是 Content-Range 。

请求报文头

Range用于请求头中,指定第一个字节的位置和最后一个字节的位置。

格式为:

Range:(unit=first byte pos) - [last byte pos]

响应头

HTTP/1.1 200 Ok(不使用断点续传方式)
HTTP/1.1 206 Partial Content(使用断点续传方式)
Content-Range用于响应头中,在发出带Range的请求后,服务器会在Content-Range头部返回当前接受的范围和文件总大小。

格式为:

Content-Range:bytes(unit first byte pos) - [last byte pos]/[entity length](文件总大小)

例子

  1. 客户端下载一个 1024K 的文件 已经下载了其中 512K;
  2. 网络中断,客户端请求续传 因此需要在 HTTP 头申明本次需要续传的片段“Range:bytes=512000- ”这个头通知服务端从文件的 512K 位置开始传输文件;
  3. 服务端收到断点续传请求,从文件的 512K 位置开始传输,并且在 HTTP 头中增加“Content-Range:bytes 512000- /1024000”并且此时服务返回的 HTTP 状态码应该 206,而不是 200。

关于多线程

断点续传是被动的增量下载,多线程是主动的分片下载,但使用都是 Range 模式。


calimanco
1.4k 声望766 粉丝

老朽对真理的追求从北爱尔兰到契丹无人不知无人不晓。