Http權威指南筆記(十)——認證

現在大多數網站都會在cookie等客戶端識別機制的基礎上建立自己的認證機制。但是HTTP規範中提供的原生認證機制還是有必要了解下,瞭解這些後才能更好理解那些自己建立的認證機制。

HTTP原生認證功能一般分爲基本認證摘要認證。基本認證相對簡單,但是安全性相對較弱,摘要認證要複雜一些,當然安全性也會高一些。在介紹這兩種認證方式之前,我們先看下HTTP中涉及到認證的一些通用概念。

1 HTTP認證機制

所謂認證就是出具一定的東西證明你的身份。HTTP中提供了一個**質詢 / 響應(challenge/response)**框架來提供認證功能。

1.1 認證過程

當Web服務器收到一條HTTP請求時,如果該請求的資源是需要認證的,服務器就會返回一個”認證質詢“的響應,客戶端收到該響應後,會要求用戶提供響應的認證資料(如用戶名/密碼等),隨後客戶端會攜帶上認證證書重新發起請求,服務器收到後會對該請求的證書進行驗證,如果驗證通過就返回正常所需響應,如果驗證失敗,可以返回一條錯誤信息或者再次返回一條”認證質詢“的響應。整個過程如下圖所示:
HTTP認證過程

1.2 認證有關的協議和首部

HTTP 通過一組可定製的控制首部,爲不同的認證協議提供了一個可擴展框架。總體來說根據上述的認證步驟可以將首部概括爲下表的內容:

步驟 首 部 描 述 方法/狀態
請求 第一條請求沒有認證信息 GET
質詢 WWW-Authenticate 服務器用 401 狀態拒絕了請求,說明需要用戶提供用戶名和密碼。服務器上可能會分爲不同的區域,每個區域都有自己的密碼,所以服務器會在 WWW-Authenticate 首部對保護區域進行描述。同樣,認證算法也是在 WWW-Authenticate 首部中指定的 401 Unauthorized
授權 Authorization 客戶端重新發出請求,但這一次會附加一個 Authorization 首部,用來說明認證算法、用戶名和密碼 GET
成功 Authentication-Info 如果授權證書是正確的,服務器就會將文檔返回。有些授權算法會在可選的 Authentication-Info 首部返回一些與授權會話相關的附加信息 200 OK

將上述的步驟和首部結合起來,描述認證過程如下圖所示:
HTTP認證步驟
當然,上面的步驟只是一個概括性描述,實際使用過程中,根據使用的認證協議不同,中間可能會有一些不同。後面會具體介紹HTTP官方提供的兩種認證協議(基本認證和摘要認證)。

1.3 安全域

所謂的安全域就是,將一組資料統組織管理起來,形成一個訪問權限控制。這個訪問權限控制點所有資料組合起來就是一個安全域。有些時候我們需要將不同的資源組織成不同的安全域進行不同的訪問權限控制,比如對於一個公司普通職員來說,財務數據屬於一個公司保密資料,只有特定認證後的員工才能訪問,所以需要建立一個安全域。對於員工的的個人家庭文檔,屬於個人隱私,這個時候又會建立一個安全域。
在HTTP中,在WWW-Authenticate中提供了一個realm指令,用於指定安全域。在上面1.2小節的示例中,我們的WWW-Authenticate就包含一個”Family“的安全域。起始本質就是提供一個字符串作爲一個安全域的標識。

介紹完HTTP中的一些認證基本知識,下面我們分別對基本認證和摘要認證進行一些簡單的闡述介紹。

2 基本認證

2.1 基本認證過程

在基本認證中,Web服務器如果需要提出質詢,可以通過返回一個401狀態碼,並用 WWW-Authenticate 響應首部指定要訪問的安全域,客戶端在收到該響應後,就會要求用戶提供用戶名/密碼,然後將這些信息進行一些處理後,用Authorization發送給服務器。具體認證過程參考前面的1.2小節即可,那一小節的舉例就是基本認證的步驟。
基本認證過程中的首部信息如下表所示:

質詢/響應 首部語法及描述
質詢(服務器發往客戶端) 網站的不同部分可能有不同的密碼。域就是一個引用字符串,用來命名所請求的文檔集,這樣用戶就知道該使用哪個密碼了:
WWW-Authenticate: Basic realm=quoted-realm
響應(客戶端發往服務器) 用冒號(:)將用戶名和密碼連接起來,然後轉換成 Base-64 編碼,這樣在用戶名和密碼中包含國際字符會稍微容易一些,也能儘量避免通過觀察網絡流量並只進行一些粗略的檢查就可以獲取用戶名和密碼情況的發生:
Authorization: Basic base64-username-and-password

可以看到,基本認證過程中是沒有使用到Authorization-Info首部的。

2.2 基本認證的加密算法

HTTP 基本認證將(由冒號分隔的)用戶名和密碼打包在一起,並用 Base-64 編碼方式對其進行編碼。Base-64編碼簡單說就是將8個字節序列劃分爲6個字節的塊,每個塊會在一個特殊的由64個字符組成的字母表中選擇一個字符。詳細的信息感興趣的朋友可以自己搜索下。

2.3 代理認證

代理作爲服務器角色的時候,也是可以要求對客戶端進行認證的,比如一些公司或者組織會統一使用一個認證代理,集中管理員工對公司資源的訪問策略。代理認證和服務器認證的過程基本一致,只是首部稍微有些差別,如下表所示:

Web服務器 代理服務器
Unauthorized status code: 401 Unauthorized status code: 407
WWW-Authenticate Proxy-Authenticate
Authorization Proxy-Authorization
Authentication-Info Proxy-Authentication-Info

2.4 基本認證的問題

基本認證雖然實現簡單,但是其在安全性上非常薄弱,存在以下幾個安全問題:

  1. 基本認證會直接發送用戶名和密碼,雖然用戶名和密碼是經過Base-64編碼的,但是這種編碼是可逆的,而且非常容易反向編碼獲取真正的用戶名和密碼。相當於是一種“明文”傳輸了。
  2. 針對第一個問題,如果是專用的客戶端和服務器,協商好之後,可以在編碼之前就對用戶名和密碼進行加密處理,即便是這樣處理後,也可以攔截到加密後的用戶名和密碼,進行重放攻擊。
  3. 基本認證中客戶端對服務器沒有進行驗證,惡意第三方很容易冒充服務器接收客戶端發送的用戶名和密碼。

所以我們如果只是單純是用基本認證,沒有配合其他安全措施(如SSL)使用的話,安全性是非常弱的。所以這種一般適用於一些對安全性要求不太高的情形,如:公司對內部員工的管理上。

3 摘要認證

上面介紹了基本認證,可以看到基本認證的安全性很低,所以HTTP提供了另外一種安全性更高,當然實現也相對複雜一些的摘要認證方式。
相對於基本認證,摘要認證的安全性提升主要有一下幾個方面:

  • 不會發送明文用戶名和密碼
  • 防止惡意的重放認證攻擊
  • 可以有選擇的防止對報文內容的修改

3.1 摘要認證改進的地方

3.1.1 對於密碼的保護

上面提到,摘要認證不會明文發送密碼。因爲摘要認證是通過對數據(這裏簡單看成密碼)進行不可逆的加密方式(常見的是MD5)後進行傳輸的。即使攔截到傳輸的內容,也沒法逆轉獲得密碼。服務收到認證請求後,也只能是通過對密碼進行同樣方式加密和對比加密後的摘要,而不是直接對比密碼,看是否一致。

3.1.2 防止重放攻擊

如果只是對認證信息進行不可逆的加密處理,攻擊者在攔截到摘要信息後,同樣可以發動重放攻擊,所以我們還需要一個手段來防止這樣的事情。在摘要認證中,服務器可以發送一個隨機數給客戶端,客戶端在計算摘要的時候,需要加上該隨機數。由於該隨機數會經常變化,所以加密後的摘要也會經常變化。這樣就可以攻擊者攔截到摘要發起重複攻擊了。

3.1.3 摘要認證的握手機制

摘要認證和基本認證的流程大體上一致。但是摘要認證還添加了一些新的選項,具體握手機制如下圖所示:
摘要認證握手機制
從上面可以看到,這裏客戶端也可以選擇對服務器質詢,所以相對於基本認證,不容易受到一些冒充服務器騙取信息。
這裏我們將基本認證和摘要認證的過程進行對比如下:
基本認證和摘要認證對比

3.2 摘要的計算

摘要認證的核心就是通過一些計算,得出摘要的過程。得出摘要的安全性越高,那麼摘要認證的安全性也就越高。所以這裏我們簡單介紹下摘要認證的計算過程。

3.2.1 摘要計算的數據來源

上面我們基本認證加密的時候,數據就是用戶名+冒號(:)+密碼。摘要認證的數據來源就要相對複雜一些了。摘要認證中的數據總的分爲兩個部分:

  • A1——一個包含安全信息(用戶名、密碼、保護域和隨機數等)的數據塊
    這裏的A1根據提供的算法不同,數據組成也不太相同,目前RFC2617定義了兩種算法(MD5和MD5-sess),A1對應的數據分別如下:
算法 A1的值
MD5 A1 = <user>:<realm>:<password>
MD5-sess A1 = MD5(<user>:<realm>:<password>):<nonce>:<cnonce>

這裏的nonce和cnonce分別代表服務器隨機數和客戶端隨機數

  • A2——一個包含報文中非保密屬性的數據塊(比如 URL、請求方法和報文實體的主體部分等)
    這個數據一般只和報文自身信息相關,同樣根據RFC2617定義的不同保護質量(qop),A2包含的數據也不相同,具體如下表所示:
qop A2
未定義 <request-method>:<uri-directive-value>
auth <request-method>:<uri-directive-value>
auth-int <request-method>:<uri-directive-value>:H(<request-entitybody>)

可以看到默認的是採用auth的方式,除非指定qop="auth-int"。這裏的H(<request-entitybody>)代表一種算法,下面會詳細介紹。

3.2.2 摘要計算函數

摘要計算過程中,涉及到兩個函數,這裏我們命名如下:
H(d)H(d)——這個函數就代表一個加密函數,一般指MD5,所以等價於MD5(d)MD5(d)。這裏的d就代表待加密的數據。
KD(s,d)KD(s,d)——這個就是最終使用的摘要計算函數。一般也是使用MD5的方式,只是這裏的輸入數據s和d分別代表上面提到的保密數據和非保密數據連個部分,一般使用冒號(:)將兩部分進行連接。所以最終這兩個公司可以變形爲如下:
H(d)=MD(d)H(d) = MD(d)
KD(s,d)=H(concatenate(&lt;secret&gt;:&lt;data&gt;))KD(s,d) = H(concatenate(&lt;secret&gt;:&lt;data&gt;))

3.2.3 摘要算法總結

根據上面兩個小節的內容,根據RFC2617的定義,這裏將算法彙總如下:

qop 摘要算法 備 注
未定義 KD(H(A1), <nonce>:H(A2)) 不推薦
auth 或 auth-int KD(H(A1), <nonce>:<nc>:<cnonce>:<qop>:H(A2)) 推薦

總結起來就是,在未定義qop的情況下,爲了和RFC2069兼容,使用保密信息加上隨機數的方式,如果定了qop爲auth或者auth-int,除了保密信息和服務器隨機數,還需要加上隨機數計數(nc),客戶端隨機數(conce)和qop。這裏如果我們將H和KD函數替換爲我們常用的MD5加密方式,就可以得到如下算法:

qop 算  法 展開的算法
未定義 <undefined>
MD5
MD5-sess
MD5(MD5(A1):<nonce>:MD5(A2))
auth <undefined>
MD5
MD5-sess
MD5(MD5(A1):<nonce>:<nc>:<cnonce>:<qop>:MD5(A2))
auth-int <undefined>
MD5
MD5-sess
MD5(MD5(A1):<nonce>:<nc>:<cnonce>:<qop>:MD5(A2))

從上面的表格可以看到,算法總結起來無外乎就是
MD5(MD5(A1):&lt;nonce&gt;:MD5(A2))MD5(MD5(A1):&lt;nonce&gt;:MD5(A2))

MD5(MD5(A1):&lt;nonce&gt;:&lt;nc&gt;:&lt;cnonce&gt;:&lt;qop&gt;:MD5(A2))MD5(MD5(A1):&lt;nonce&gt;:&lt;nc&gt;:&lt;cnonce&gt;:&lt;qop&gt;:MD5(A2))
只是根據算法和qop的不同,會影響到A1和A2兩個數據塊的組成而已。

3.2.4 隨機數的選擇

摘要認證的安全和隨機數的選擇有很大關係,隨機數選擇就顯得比較重要。RFC2617中建議隨機數採用如下方式生成:
BASE64(time-stamp H(time-stamp “:” Etag “:” private-key))
這裏的time-stamp是服務器產生的時間戳。Etag是與請求資源相關的HTTP報文中Etag首部的值,private-key就是一個服務器自己知道的一個值。
通過這種方式計算出來的隨機數,time-stamp可以控制我們隨機數的有效期,加上Etag可以可以在資源更新後要求重新認證,防止重放攻擊。
實際開發中,通常一般GET請求會是使用一個時間戳來控制有效期,而如POST或者PUT之類的請求,一般是使用一次性隨機數,防止重複提交。

3.2.5 對稱認證

所謂對稱認證,就是客戶端也可以對服務器進行質詢認證。同樣是通過客戶端提供隨機值來實現的。服務器可以根據這個隨機值和共享的保密信息來生成摘要,然後放在響應的Authoriztion-Info首部中返回給客戶端。
不過這個和請求認證摘要的計算方法稍微有點不同,因爲響應中沒有方法,所以相對於請求摘要A2數據區會缺少方法這一部分的數據,對比如下表所示:

qop A2數據
請求 響應
未定義 <request-method>:<uri-directive-value> :<uri-directive-value>
auth <request-method>:<uri-directive-value> :<uri-directive-value>
auth-int <request-method>:<uri-directive-value>:H(<request-entity-body>) :<uri-directive-value>:H(<response-entity-body>)

3.3 摘要認證會話和預授權

客戶端響應對保護空間的 WWW-Authenticate 質詢時,會啓動一個此保護空間的認證會話(與受訪問服務器的標準根結合在一起的域就定義了一個“保護空間”)。這個認證會話會一直持續到客戶端收到了另一“保護空間”的質詢,所以客戶端在此期間應該記住一些與認證有關的值(如:用戶名、密碼、隨機數、隨機計數等),方便隨時構建Authorization首部。
由於隨機數是有時效性的,所以可能某個時候隨機數就失效了,這個時候服務器收到隨機數過時的認證請求後,應該返回一個攜帶新的隨機數的狀態碼爲401的響應,同時在響應中指定stale=true,用於告知客戶端,隨機數過期了,可以使用之前的用戶名和密碼等信息,加上新的隨機數來重新認證即可。

如果我們對這種請求/質詢的對話不進行優化,那麼意味着每次我們發起請求都會走一遍請求/質詢的流程。如果我們客戶端除了第一次請求/質詢後,後面每次發起請求的時候能夠預先算出摘要,發起請求的時候直接將摘要放入Authorization首部,那麼就不用每次都走一遍請求/質詢的流程,這樣會節約資源也會增加效率,這裏所說的預先算出正確的摘要,就是預授權。下圖展示了預授權處理後和普通請求/質詢之間的流程差異:
預授權
通過前面的介紹,客戶端計算摘要,如果我們第一次請求/質詢後將用戶名、密碼等信息保存下來後,剩下的就是缺少服務器提供的隨機值,如果我們能夠預先知道隨機值,那麼就能正確的計算摘要了。一般有下面三種方式,可以讓服務器預先將隨機值給到客戶端。

  1. 預先生成一個隨機數
    可以在 Authentication-Info 成功首部中將下一個隨機數預先提供給客戶端。這個首部是與前一次成功認證的 200 OK 響應一同發送的。如下:
    Authentication-Info: nextnonce="<nonce-value>"
    雖然這樣可以讓客戶端預先計算摘要的目的,但是這個方法也有個就是不支持管道的特性。這種處理,意味着必須在上一個請求發起後,並收到響應(整個事務流程完成)後才能發起下一個請求,就不能利用管道連續發出多個請求了。
  2. 受限隨機值重用機制
    這種不是預先生成隨機值並返回給客戶端,而是將一個隨機值設置一個可重用的次數或者時間。那麼在這個有效期內,客戶端就可以直接計算出摘要,同時這個方法還不破壞管道的特性。當這個隨機值失效後,返回一個401錯誤並攜帶stale=true和新隨機值的信息即可。但是也有個缺點,因爲隨機值是可以重用的,所以可能有受到重放攻擊的風險。所以這個時候需要尋找到一個相對平衡的有效性限制。
  3. 同步生成隨機數
    這種方式,就是服務器和客戶端共享一些信息,然後用同一套機制生成隨機數,那麼客戶端就可以隨時自己生成隨機數來進行摘要計算。但是這種方式對隨機數的生成方式要做好保護措施,如果被第三方預測或者獲取到,那就危險了。

3.4 增強保護質量

可以在三種摘要首部中提供 qop 字段:WWW-Authenticate、Authorization 和 Authentication-Info。該字段就是用於協商保護質量的。
服務器首先在 WWW-Authenticate 首部輸出由逗號分隔的 qop 選項列表。然後客戶端從中選擇一個它支持且滿足其需求的選項,並將其放在 Authorization 的 qop 字段中回送給服務器。
爲了兼容RFC2029,所以qop被設定爲一個可選字段。但是爲了安全性着想,應該儘量滿足提供qop支持並使用qop。
在RFC2617中提供了兩種qop的可選值:也就是我們前進已經介紹了的auth和auth-int,兩者的區別是auth-int帶有報文完整性保護支持,因爲使用auth-int的時候,使用H(d)算法的實時,會對報文實體而不是對報文信息繼續散列計算。

3.5 摘要認證首部

其實我們的摘要認證首部在介紹前面內容的時候,都介紹的差不多了,這裏我們結合前面的基本認證做一個彙總表格,如下:

階段 基  本 摘  要
質詢 WWW-Authenticate: Basic realm="<realm-value>" WWW-Authenticate: Digest
realm="<realm-value>“
nonce=”<nonce-value>"
[domain="<list-of-URIs>"]
[opaque="<opaque-token-value>"]
[stale=<true-or-false>]
[algorithm=<digest-algorithm>]
[qop="<list-of-qop-values>"]
[<extension-directive>]
響應 Authorization: Basic <base64(user:pass)> Authorization: Digest
username="<username>“
realm=”<realm-value>“
nonce=”<nonce-value>"
uri=<request-uri>
response="<32-hex-digit-digest>"
[algorithm=<digest-algorithm>]
[opaque="<opaque-token-value>"]
[cnonce="<nonce-value>"]
[qop=<qop-value>]
[nc=<8-hex-digit-nonce-count>]
[<extension-directive>]
Info n/a Authentication-Info:
nextnonce="<nonce-value>">
[qop="<list-of-qop-values>"]
[rspauth="<hex-digest>"]
[cnonce="<nonce-value>"]
[nc=<8-hex-digit-nonce-count>]

這裏只是簡單的介紹了首部,起始摘要相關的首部是非常複雜的,感興趣的朋友可以自己去找找資料看看。

這裏摘要認證和基本認證一樣,我們不能簡單實現了認證就完事,還需要考慮一些其他問題:

  • 多重質詢:服務器可以提供一個可供客戶端選擇的質詢列表,客戶端選擇自己支持一種方式
  • 差錯處理:如果指令不對或者不當,服務器就應該返回400 Bad Request響應,同時記錄下該次請求,如果發現有多次相同錯誤,有可能是攻擊現象
  • 重寫URI:代理可以重寫URI,如對主機名標準化、轉義一些字符等,這樣URI被修改後,可能造成認證失敗
  • Cache:同基本認證一樣,對於有Authorization首部的響應,我們緩存的時候,應當謹慎處理。

到這裏,我們認證的內容就介紹完了,認證是一個非常複雜的東西,本篇也僅僅就是簡單介紹了一些基本的東西,真正感興趣或者想要掌握的朋友,還是建議去看一些專門介紹的資料。特別是對於安全性這一塊的內容,更加要注意,不管是基本認證和摘要認證,安全性和SSL比起來(後面會有文章來介紹HTTPS),都顯得很薄弱,要特別注意處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章