HTTP 认证

编辑于2018年04月30日

某些页面、数据、功能等只想让特定的人甚至本人访问,为了实现这个目标,那么必不可少的就是认证功能,例如:常见的输入用户名、密码的表单验证或者移动端的手机短信验证等。但是,今天的主角并不是它们,而是由 HTTP 提供的原生认证方式。以前,对 HTTP 认证有一些了解,但并没有实际应用,前段时间在工作中需要接入一些摄像头设备,它们大多采用了 HTTP 认证的方式,这激发了我弄清楚 HTTP 认证机制的兴趣,尤其是摘要认证。下面进入到本文的主题,说明 HTTP 认证机制。

BASIC 认证

BASIC 认证(基本认证)是 HTTP/1.0 就定义的认证方式,即便是现在仍有一部分的网站会使用这种认证方式。

BASIC 认证的步骤

BASIC 认证的步骤非常简单,主要包括下面三个步骤:

  1. 客户端请求一个需要授权的资源,服务器返回状态码 401 Authorization Required。同时,设置 WWW-Authenticate 响应头字段,指定认证方式(BASIC)以及安全域字符串(realm)。
  2. 客服端在收到 401 状态码后,需要将用户名和密码用冒号(:)连接起来,并对连接后的字符串进行 Base64 编码处理,然后将编码过的字符串添加到请求头 Authorization 字段中。
  3. 服务器收到头部包含 Authorization 字段的请求后,会对认证信息进行校验,如果验证通过,则返回受保护的资源。

下图摘抄自《图解 HTTP》,图中形象的说明了 HTTP 基本认证的流程。

basic_auth

在上图第二步中,如果客户端为浏览器,则会弹出一个对话框提示用户输入用户名和密码,如下图所示。

basic_auth_dialog

BASIC 认证的优缺点

BASIC 认证提供了一个简单的身份认证方式,可以让我们很方便的对服务器上的资源提供保护,实现起来十分简单。

但是它的缺点较为明显,用户名和密码只是经过了 Base64 编码,相当于明文传输,在 HTTP 等非加密通信的线路上进行 BASIC 认证的过程 中,如果被人窃听,被盗的可能性极高。因为安全性达不到大多数 Web 站点的要求,因此这种认证方式不太常用,通常用在简单的内部系统中。

摘要认证

为了解决 BASIC 认证存在的弱点,从 HTTP/1.1 起就有了 DIGEST 认 证。在认证流程上同 BASIC 认证一样,采用质询/响应的方式,只是在认证信息的生成和校验上做出了改进,不再明文传递密码。

DIGEST 认证的步骤

DIGEST 认证的步骤和 BASIC 步骤完全一样,只是头部的字段不同。这里就不重复叙述流程,重点介绍服务器返回 401 状态码时,WWW-Authenticate 字段的内容以及客户端提交的 Authorization 字段中的内容。

WWW-Authenticate 响应头

如果服务器接收到访问受保护对象的请求并且没有收到合法的 Authorization 请求头,服务器将会响应 “401 Unauthorized” 状态码以及 WWW-Authenticate 响应头,头部内容遵循下面的格式:

challenge        =  "Digest" digest-challenge

digest-challenge = 1#( realm | [ domain ] | nonce | [ opaque ] |[ stale ] | [ algorithm ] | [ qop-options ] | [auth-param] )
domain           = "domain" "=" <"> URI ( 1*SP URI ) <">
URI              = absoluteURI | abs_path
nonce            = "nonce" "=" nonce-value
nonce-value      = quoted-string
opaque           = "opaque" "=" quoted-string
stale            = "stale" "=" ( "true" | "false" )
algorithm        = "algorithm" "=" ( "MD5" | "MD5-sess" | token )
qop-options      = "qop" "=" <"> 1#qop-value <">
qop-value        = "auth" | "auth-int" | token

上面各个字段内容的含义如下:

  • realm:显示给用户看的字符串,用户通过该字符串可以知道应该使用哪个用户名和密码。该字符串至少包含执行鉴权的主机名和可以额外指明哪些用户有权限的集合。例如:“registered_users@gotham.news.com”。
  • domain:包含在引号中用空格分隔的 URL 列表,表示受保护的区域。
  • nonce:每次 401 响应时由服务器生成的唯一字符串,建议使用 base64 或者 16 进制的格式。因为该字符串在报头行中传递时会包裹在引号内,因此不允许使用双引号。nonce 的内容取决于生成的方式,nonce 的好坏取决于生成方式的选择,例如通过下面方式生成的 base64 编码的字符串:time-stamp H(time-stamp ":" ETag ":" private-key)。time-stamp 是服务器上的生成时间或者其他不重复的值,ETag 是 HTTP ETag 响应头的值,private-key 是只有服务器才知道的数据。如果是这样的格式,服务器在收到客户端 Authentication 头时会重新计算 hash,如果不正确或者时间过去太久则拒绝该请求。通过这种方式,服务器可以限制 nonce 的有效期。ETag 中的内容可以防止对以更新的内容再次请求。
  • opaque:由服务器指定的字符串,客户端在后续指向同一个受保护区间的请求中应该在 Authorization 头中原样返回,建议使用 base64 或者 16 进制数。
  • stale:表明之前的请求由于 nonce 值过期而被拒绝的标识。如果 stale 的值是 TRUE(不区分大小写),客户端可能希望简单的使用新的加密信息重新请求,而不用麻烦用户提供新的用户名和密码。只有在 nonce 的摘要正确(说明客户端知道用户名和密码)但是内容不合法时,服务器才设置 stale 为 TRUE,如果 stale 不是 TRUE,这说明用户名、密码不正确。
  • algorithm:表明生成摘要的算法,默认是 MD5。
  • qop-options:可选项,用来向后兼容 RFC 2069[6],在所有兼容此版本的摘要认证的实现中应该指定此字段。如果被指定了,内容应该是用引号引起来的表示服务器支持的“保护质量”的一个或多个字符。“auth” 表示鉴权;“auth-init” 表示带完整性保护的鉴权。
  • auth-param:未来的扩展字段,任何无法识别的内容都将被忽略。

Authorization 请求头

客户端收到 401 后,重新提交请求,并带上 Authorization 请求头,头部内容遵循下面的格式:

credentials      = "Digest" digest-response
digest-response  = 1#( username | realm | nonce | digest-uri | response | [ algorithm ] | [cnonce] | [opaque] | [message-qop] | [nonce-count] | [auth-param] )
username         = "username" "=" username-value
username-value   = quoted-string
digest-uri       = "uri" "=" digest-uri-value
digest-uri-value = request-uri; As specified by HTTP/1.1
message-qop      = "qop" "=" qop-value
cnonce           = "cnonce" "=" cnonce-value
cnonce-value     = nonce-value
nonce-count      = "nc" "=" nc-value
nc-value         = 8LHEX
response         = "response" "=" request-digest
request-digest   = <"> 32LHEX <">
LHEX             =  "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "a" | "b" | "c" | "d" | "e" | "f"

opaque 和 algorithm 的值必须是 WWW-Authenticate 中提供的可选值。
上面各个字段内容的含义如下:

  • response:由 32 个经过计算的 16 进制数组成的字符串,证明用户知道密码。
  • username:用户名。
  • digest-uri:请求头中的 URI,这里重复指定是因为代理在传输时允许修改请求。
  • qop:表明客户端所采用的“保护质量”,如果服务器在 WWW-Authenticate 头中添加了 qop 项,则客户端必须指定。
  • cnonce:如果 qop 指定了,此项也必须指定,而如果服务器没有在 WWW-Authenticate 中添加 qop 时,此项不能指定。该字段的值是客户端提供的字符串,它由客户端和服务器共同使用,用来避免选择纯文本攻击、提供共同鉴别、提供某些消息的完整性保护。
  • nonce-count:qop 指定了,此项必须指定,而如果服务器没有在 WWW-Authenticate 中添加 qop 时,此项不能指定。该字段的值是用来统计客户端请求次数的 16 进制数,用来给服务器判断是否重放请求。
  • auth-param:未来的扩展字段,任何无法识别的内容都将被忽略。

请求摘要信息计算规则

前面说明了 Authenticate 头中的一些字段及编码,下面介绍请求摘要(request-digest)的值如何计算。

  1. 如果 qop 为 auth 或者 auth-int,则计算公式为:Digest = hash(hash(<A1>):<nonce>:<nc>:<cnonce>:<qop>:hash(<A2>))
  2. 如果 qop 未定义,则计算公式为:Digest = hash(hash(<A1>):<nonce>:hash(<A2>))

其中 A1 的计算规则如下:

  1. 如果算法为 MD5,则 A1 = <username>:<realm>:<password>
  2. 如果算法为 MD5-less,则 A1 = hash(<username>:<realm>:<password>):<nonce>:<cnonce>

A2 的计算规则如下:

  1. 如果 qop 未定义或者是 auth,则 A2 = <request-method>:<digest-uri-value>
  2. 如果 qop 为 auth-int,则 A2 = <request-method>:<digest-uri-value>:hash(<request-entity-body>)

总结

HTTP 认证在现在的 Web 应用中使用得不是特别多,主要还是通过表单发送用户名和密码,然后结合 sessiong/cookie、token、JWT 等保持登录状态,以及使用 OAuth2.0 之类的授权协议,这些认证机制相比 HTTP 认证更加的灵活、安全。

我们需要做到的是了解 HTTP 认证的基本流程,大致知道认证信息如何生成即可。为了能更深的理解 HTTP 认证的流程,我简单的实现了 HTTP 的基本认证和摘要认证,如果有兴趣可以访问 github 上的源码

参考:

  1. 《图解 HTTP》
  2. RFC2617

希望对您能有帮助,打赏随意