HTTP系列(四):HTTP的基础概念概述

在上一篇文章中, 我们讨论了网站用多种方法去标识浏览网站的用户(身份)。

标识本身仅仅表示一个声明, (用户通过声明)表明自己的身份, 然而除此之外, 并没有其他证据可以证明用户的身份。

认证, 用另一种方式来说, 就是用户证明自身身份的证据, 展示个人ID和输入用户密码是两种证明自身身份常见的方法。

通常情况下,只有通过认证, 网站才能为用户提供敏感资源。

HTTP自身拥有认证机制, 该机制允许服务器(向用户)提出质询并获取其所需的认证信息。你将会在文章中了解到什么是认证和认证是如何进行的。文章中还会涵盖每一种认证方式的优缺点, 并且分析它们是否具有独当一面的能力。(偷偷剧透一下, 它们才没这个能力呢!)

这篇文章是HTTP系列的第四部分。

文章概要:

  • HTTP认证的工作方式
  • Basic认证(基本认证)
  • Digest认证(摘要认证)

在进一步探索什么是HTTP认证机制之前, 我们一起来看看什么是HTTP认证吧!

HTTP认证的工作方式

认证是Web服务器标识用户的一种方式。用户需要摆出证据, 证明其有获得所请求资源的权限。通常, 证明的过程需要一组用户名和密码, 并且, 输入的用户名和密码必须经服务器(验证后)认同是有效的, 然后才由服务器判断该用户是否有获取该资源的权限。

HTTP提供了两种认证协议:

  • Basic认证(基本认证)
  • Digest认证(摘要认证)

在深入了解上述两个协议之前, 我们来大概过一遍基础概念。

质询(challenge)/响应(response)认证框架

质询/响应认证是什么意思?

就是说, 在某个客户端发送请求的时候, 服务器不会立即响应, 而是(向客户端)返回认证要求。该认证要求要求用户通过输入私密信息(用户名及密码)来提供标识身份的证明。

之后, 客户端会重复发送请求来提供(身份)凭证, 如果该身份凭证被服务器认证是有效的, 那么用户可以得到所请求的响应资源。如果(身份)凭证被认为无效, 那么服务器会重新发出质询, 或直接发送(认证信息)错误报文。

注:加入用户名密码错误的话, 服务器会返回401

与认证相关的请求/响应首部字段

WWW-Authenticate响应首部字段, 是服务器用来(向客户端)发出质询的, 该首包含了认证协议和认证域这两部分内容。

在用户输入(身份)凭证之后, (客户端)会重新发送请求报文。然后, 客户端会发送附有Authorization首部字段的请求报文, 该首部字段包含了认证方式(Basic)和一组用户密码。

如果(身份)凭证通过验证, 服务器会返回响应, 并在响应中选用Authentication-Info首部字段附上附加信息——该首部字段是非必需的, 所以可有可无。

安全认证域(Security Realms)

认证域(又称防护域, protection space)在服务器上提供了不同访问权限关联不同资源组的方法

实际上, 认证域的存在就意味着, 用户访问(不同的)资源需要输入不同的身份凭证。

服务器拥有多个认证域。

/admin/statistics/financials.txt -> Realm=”Admin Statistics”
比如, 上面的示例就是存放网站统计信息的认证域——只允许网站管理员访问(其中的资源)。

/images/img1.jpg -> Realm = “Images”
还有存放网站图片的认证域——即使不是管理员, 也能访问和上传图片。

当你尝试访问”financials.txt”时, 服务器会向你发出质询, 并返回如下响应:

1
2
HTTP/1.0 401 Unauthorized
WWW-Authenticate: Basic realm="Admin Statistics"

点击https://tools.ietf.org/html/rfc7235#section-2.2可了解更多认证域。

简单的HTTP认证实例

现在, 让我们用简单的HTTP认证实例为这一小节内容画上一个完美的句号吧!(basic认证会在后文继续讲述)

  1. 用户代理 -> 服务器
    用户请求访问服务器上的图片。

    1
    2
    GET /gallery/personal/images/image1.jpg HTTP/1.1
    Host: www.somedomain.com
  2. 服务器 -> 用户代理
    服务器(向客户端)发送认证要求。

    1
    2
    HTTP/1.1 401 Access Denied
    WWW-Authenticate: Basic realm="gallery"
  3. 用户代理 -> 服务器
    用户通过表单输入标识自己。

    1
    2
    GET /gallery/personal/images/image1.jpg HTTP/1.1
    Authorization: Basic Zm9vOmJhcg==
  4. 服务器 -> 用户代理
    服务器验证(身份)凭证成功后, 发送状态码”200 OK”及图片。

    1
    2
    3
    HTTP/1.1 200 OK
    Content-type: image/jpeg
    ...<image data>

看起来不是那么复杂, 对吗?

现在, 我们来进入更深层次的学习, 学习basic认证。

basic认证

basic认证是最常用且支持面最广的认证协议。从HTTP/1.0开始, basic认证就已经广泛应用了, 每一个客户端都可以使用该认证协议认证。

上面的实例描述的是basic认证过程。basic认证实现和用起来都很简单, 不过, 它存在着安全问题。

进入(basic认证)安全问题的学习之前, 不妨先了解一下basic认证是如何处理(认证过程中传输的)用户和密码的。

basic认证将用户名及密码放在一串字符串中, 两者由符号”:”分割。然后, 用base64编码字符串。不过, 无论字符串的表现形式是什么样, 混乱的字符顺序也不能保证(认证)安全, 因为它可以被轻易地解开。

实际上, base64编码不是为了加密, 而是为了保证用户名及密码在通过HTTP协议传输之后具有可移植性。不过,在HTTP首部字段中不能使用通用字符, 才是编码的主要原因。

1
2
GET /gallery/personal/images/image1.jpg HTTP/1.1
Authorization: Basic Zm9vOmJhcg==

例子中的字符串”Zm9vOmJhcg==”解码后只是”foo:bar”(这一小段字符串)。

因此, 每个可以收到请求的(代理/服务器)都能轻易地解码和使用(传输的身份)凭证。

更糟糕的是, 恶意的第三方仍然可以通过发送(编码后的)混乱字符序列达到同样的效果, 因此对用户名及密码进行编码不会有任何帮助。

没有任何可以用来防止代理或其他形式的攻击篡改请求主体(数据), 同时还能保证请求主体完好无损的措施。

因此, 如你所见, basic认证称不上是一个完美的认证机制。

尽管如此, basic认证还是可以用来防止(无权限用户)意外访问受保护的资源, 并(为用户)提供一定程度的个性化内容。

为了让(认证)更加安全实用, 可以用基于SSL的HTTPS协议对basic认证进行扩展, 相关内容我们会在本系列的第五部分详述。

然而, 有些人认为, basic认证的安全与否取决于运输机制(的安全性)

digest(摘要)认证

与简单且不安全的basic认证相比, digest认证是一个更加安全可靠的选择。

那么, 它是如何认证的呢?

digest认证将多个nonceMD5哈希加密算法结合使用, 以此来隐藏密码(使账号)免于遭受多种恶意攻击的侵害。

注: nonce本身是用base64加密的。

这个(认证过程)听起来好像很复杂, 不过在你看完实例之后, 就会觉得清晰多了。

实例

  1. 用户代理 -> 服务器

    1
    2
    GET /dir/index.html HTTP/1.0
    Host: localhost

    客户端发送不含认证(信息)的请求。

  2. 服务器 -> 用户代理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    HTTP/1.0 401 Unauthorized
    WWW-Authenticate: Digest realm="shire@middleearth.com",
    qop="auth,auth-int",
    nonce="cmFuZG9tbHlnZW5lcmF0ZWRub25jZQ",
    opaque="c29tZXJhbmRvbW9wYXF1ZXN0cmluZw"
    Content-Type: text/html
    Content-Length: 153
    <--空行--!>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8" />
    <title>Error</title>
    </head>
    <body>
    <h1>401 Unauthorized.</h1>
    </body>
    </html>

    服务器对客户端发出质询, 要求其用digest认证进行认证, 并向客户端发送(认证时)所需的信息。

  3. 用户代理 -> 服务器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    GET /dir/index.html HTTP/1.0
    Host: localhost
    Authorization: Digest username="Gandalf",
    realm="shire@middleearth.com",
    nonce="cmFuZG9tbHlnZW5lcmF0ZWRub25jZQ",
    uri="/dir/index.html",
    qop=auth,
    nc=00000001,
    cnonce="0a4f113b",
    response="5a1c3bb349cf6986abf985257d968d86",
    opaque="c29tZXJhbmRvbW9wYXF1ZXN0c

    客户端将计算出响应码与用户名、认证域(real)、URI、nonce(随机数)、opaque、qop、nc和cnonce(客户端的随机数)这些属性一起发送出去, 这信息量真的很大。

  4. 服务器 -> 客户端

    1
    2
    3
    4
    HTTP/1.0 200 OK
    Content-Type: text/html
    Content-Length: 2345
    ... <content data>

    服务器会自己计算(服务器产生的nonce)的哈希值并与从客户端收到的nonce哈希值进行比对。若两者相同, 服务器就会发送(客户端)所请求的数据。

(步骤3的)详细说明

定义:

  • nonce、opaque —— 服务器生成的字符串, 客户端会在返回的内容中附上该信息。
  • qop(保护质量) —— 可设置一个或多个值(“auth” | “auth-in” | token)。这些值影响摘要(信息)的计算。
  • cnonce - 客户端随机数, 若设置了qop的值, 一定要生成客户端随机数。它是用来防止选择明文攻击和保证信息完整性的。
  • nc —— 若qop设置了, 必须(给服务器)发送该属性。服务器可通过nc属性的值, 检测请求是否是重新发送过来的。若(两次请求)nc属性出现了相同值, 那么说明该请求重新发送了一次。

response属性的值的计算方法:

1
2
3
4
5
6
7
8
9
10
11
HA1 = MD5("Gandalf:shire@middleearth.com:Lord Of The Rings")
= 681028410e804a5b60f69e894701d4b4

HA2 = MD5("GET:/dir/index.html")
= 39aff3a2bab6126f332b942af96d3366

Response = MD5( "681028410e804a5b60f69e894701d4b4:
cmFuZG9tbHlnZW5lcmF0ZWRub25jZQ:
00000001:0a4f113b:auth:
39aff3a2bab6126f332b942af96d3366" )
= 5a1c3bb349cf6986abf985257d968d86

如果你对response属性是如何受qop属性影响这件事感兴趣的话, 你可以看看RFC 2617

简短的总结

如你所见, digest认证不管是理解起来还是实现起来都(比basic认证)复杂多了。

尽管, digest认证比basic认证安全得多, 但还是存在遭受中间人攻击的可能。

RFC 2617中提到, digest认证是用于取代basic认证的, 因为它可弥补basic认证的不足, 同时RFC 2617中没有将digest认证仍旧不符合现代密码标准的事实隐藏起来, 不过, 该认证安全与否, 还是取决于实际实施情况。

digest认证的优点:

  • 不会在网络中发送明文密码
  • 防止重放攻击
  • 防止篡改报文

digest认证的缺点:

  • 易遭受中间人攻击
  • 不用设置大量的安全选项, 导致digest认证方法不够安全
  • 存储密码时, 不能使用强密码哈希函数

注: 缺点的最后一点, 是因为无论是密码、用户名、realm、密码的摘要都要求是可恢复的

上述的种种缺点使得digest认证未能大力推广, 反观简单的basic认证, 结合SSL使用之后比(复杂的)digest认证安全得多——因此该认证方法得到大力推广。

总结

本文为HTTP系列的一部分。

文章看到这里, 我们大致过了一遍HTTP默认提供的认证机制, 并且谈论了不同认证机制的优缺点。

希望(对你来说)这些概念不再只是屏幕上的单词, 并且下次你提到这些名词的时候, 能准确地知道它们是什么和怎么运用它们。

你应该察觉到了, 上述认证机制并不能规避(所有)安全风险, 这就是HTTPS、SSL/TLS(Transport Layer Security, 传输层安全性协定)这些概念存在的原因。下一篇文章, 我们将会更多地去讨论安全问题和如何去规避这些问题。

如果你感觉有些概念不是很清晰, 你可以看看HTTP系列第一部分第二部分第三部分

安全小讲堂

在找相关知识的时候, 发现有一个人用了一个很通俗易懂的方式来讲密码学里面的知识, 感觉很有趣。今天来尝试一波。

假设小明和小兰互相不认识, 在老师严抓早恋的背景下, 俩人都学习了写情书的新姿势, 将自己写的文字加密并发出, 然后通过情书培养感情。小草也喜欢小兰, 为了阻(heng)止(dao)他(duo)们(ai), 采取了以下攻击方式。

  1. 选择明文攻击
    小草很了解小兰(但没有学习过上面的新姿势), 知道小兰最爱在信里表示自己很”开心”, 所以信加密后出现最高频率的词, hexo这就是知道”开心”加密之后的密文是啥了。
    ————已知密文。

    如果有一天, 小兰写”开心”的次数变少了, 小草就可以找机会雪(chen)中(xu)送(er)炭(ru)了。

  2. 中间人攻击
    小明和小兰协商好, 在书信里面放入自己的信物表明自己的身份, 为了防止第三者做些乱七八糟的事情, 这个方式也是很明智了。但是未曾想, 小草在一开始协商信物的时候, 就窃取了他们的信物——这个信物就是公钥——并且将自己事先准备好的两个信物与小明和小兰原本的信物进行对调。


    这时候小草甚至还能皮一下, 篡改小明的书信, 离间他们两者之间的感情。

  3. 重放攻击
    小明给小兰送情书之前, 都会在小兰的桌上放一瓶酸奶, 作为自己给小兰写信的信号。小兰回教室看到抽屉里有信, 在思考是谁写的(发出质询), 看到酸奶(收到响应), 小兰就知道是小明给自己写情书了(通过验证)。小草找到了这个规律, 同样给小兰桌上放了一瓶酸奶(重放攻击, 二次利用认证信息), 之后在抽屉里放着离间两人的信, 这就达成了小草想要的效果。