1. 程式人生 > >HttpClient三種不同的伺服器認證客戶端方案

HttpClient三種不同的伺服器認證客戶端方案

HttpClient三種不同的認證方案: Basic, Digest and NTLM. 這些方案可用於伺服器或代理對客戶端的認證,簡稱伺服器認證或代理認證。

伺服器認證

  HttpClient處理伺服器認證幾乎是透明的,僅需要開發人員提供登入資訊(login credentials)。登入資訊儲存在HttpState類的例項中,可以通過 setCredentials(String realm, Credentials cred)和getCredentials(String realm)來獲取或設定。注意,設定對非特定站點訪問所需要的登入資訊,將realm引數置為null. HttpClient內建的自動認證,可以通過HttpMethod類的setDoAuthentication(boolean doAuthentication)方法關閉,而且這次關閉隻影響HttpMethod當前的例項。
  搶先認證(Preemptive Authentication)可以通過下述方法開啟.   client.getState().setAuthenticationPreemptive(true);   在這種模式時,HttpClient會主動將basic認證應答資訊傳給伺服器,即使在某種情況下伺服器可能返回認證失敗的應答,這樣做主要是為了減少連線的建立。為使每個新建的 HttpState例項都實行搶先認證,可以如下設定系統屬性。   setSystemProperty(Authenticator.PREEMPTIVE_PROPERTY, "true");   Httpclient實現的搶先認證遵循rfc2617.

代理認證

  除了登入資訊需單獨存放以外,代理認證與伺服器認證幾乎一致。用 setProxyCredentials(String realm, Credentials cred)和 getProxyCredentials(String realm)設、取登入資訊。   認證方案(authentication schemes)   Basic   是HTTP中規定最早的也是最相容(?)的方案,遺憾的是也是最不安全的一個方案,因為它以明碼傳送使用者名稱和密碼。它要求一個UsernamePasswordCredentials例項,可以指定伺服器端的訪問空間或採用預設的登入資訊。   Digest
  是在HTTP1.1中增加的一個方案,雖然不如Basic得到的軟體支援多,但還是有廣泛的使用。Digest方案比Basic方案安全得多,因它根本就不通過網路傳送實際的密碼,傳送的是利用這個密碼對從伺服器傳來的一個隨機數(nonce)的加密串。它要求一個UsernamePasswordCredentials例項,可以指定伺服器端的訪問空間或採用預設的登入資訊。

NTLM認證

  這是HttpClient支援的最複雜的認證協議。它M$設計的一個私有協議,沒有公開的規範說明。一開始由於設計的缺陷,NTLM的安全性比Digest差,後來經過一個ServicePack補丁後,安全性則比較Digest高。NTLM需要一個NTCredentials例項. 注意,由於NTLM不使用訪問空間(realms)的概念,HttpClient利用伺服器的域名作訪問空間的名字。還需要注意,提供給NTCredentials的使用者名稱,不要用域名的字首 - 如: "adrian" 是正確的,而 "DOMAIN\adrian" 則是錯的.   NTLM認證的工作機制與basic和digest有很大的差別。這些差別一般由HttpClient處理,但理解這些差別有助避免在使用NTLM認證時出現錯誤。   從HttpClientAPI的角度來看,NTLM與其它認證方式一樣的工作,差別是需要提供'NTCredentials'例項而不是'UsernamePasswordCredentials',對NTLM認證,訪問空間是連線到的機器的域名,這對多域名主機會有一些麻煩.只有HttpClient連線中指定的域名才是認證用的域名。建議將realm設為null以使用預設的設定。

  NTLM只是認證了一個連線而不是一請求,所以每當一個新的連線建立就要進行一次認證,且在認證的過程中保持連線是非常重要的。 因此,NTLM不能同時用於代理認證和伺服器認證,也不能用於http1.0連線或伺服器不支援持久連線的情況。

這是一個用於web瀏覽器或其他客戶端在請求時提供使用者名稱和密碼的登入認證,要實現這個認證很簡單:

我們先來看下協議裡面怎麼定義這個認證的. 1. 編碼: 將使用者名稱 追加一個 冒號(':')接上密碼,把得出的結果字串在用Base64演算法編碼.

  1. 請求頭: Authorization: 認證型別 編碼字串

來看一下客戶端如何發起請求例如,有一個使用者名稱為:tom, 密碼為:123456 怎麼認證呢?

步驟如下 1. 編碼

Base64('tom:123456') == dG9tOjEyMzQ1Ng==;

  1. 把編碼結果放到請求頭當中

    Authorization: Basic dG9tOjEyMzQ1Ng==

請求樣例客戶端

1
2
3
GET / HTTP/1.1
Host: localhost
Authorization: Basic dG9tOjEyMzQ1Ng

服務端應答

1
2
3
4
HTTP/1.1 200 OK
Date: Thu, 13 Jun 2013 20:25:37 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 53

如果沒有認證資訊

1
2
3
HTTP/1.1 401 Authorization Required
Date: Thu, 13 Jun 2013 20:25:37 GMT
WWW-Authenticate: Basic realm="Users"

驗證失敗的時候,響應頭加上WWW-Authenticate: Basic realm="請求域".

這種http 基本實現,幾乎目前所有瀏覽器都支援.不過,大家可以發現,直接把使用者名稱和密碼只是進行一次base64 編碼實際上是很不安全的,因為對base64進行反編碼十分容易,所以這種驗證雖然簡便,但是很少會在公開訪問的網際網路使用,一般多用在小的私有系統,例如,你們家裡頭的路由器,多用這種認證方式.

這個認證可以看做是基本認證的增強版本,使用隨機數+密碼進行md5,防止通過直接的分析密碼MD5防止破解. 摘要訪問認證最初由 RFC 2069 (HTTP的一個擴充套件:摘要訪問認證)中被定義加密步驟:

  1. ha1

  2. ha2 

  3. res

後來發現,就算這樣還是不安全(md5 可以用彩虹表進行攻擊),所以在RFC 2617入了一系列安全增強的選項;“保護質量”(qop)、隨機數計數器由客戶端增加、以及客戶生成的隨機數。這些增強為了防止如選擇明文攻擊的密碼分析。

d1

  1. 如果 qop 值為“auth”或未指定,那麼 HA2 為

    d3

  2. 如果 qop 值為“auth-int”,那麼 HA2 為

    d3

  3. 如果 qop 值為“auth”或“auth-int”,那麼如下計算 response:

    d4

  4. 如果 qop 未指定,那麼如下計算 response:

    d5

好了,知道加密步驟,下面我們用文字來描述一下;

最後,我們的response 由三步計算所得. 1. 對使用者名稱、認證域(realm)以及密碼的合併值計算 MD5 雜湊值,結果稱為 HA1。

HA1 = MD5( "tom:Hi!:123456" ) = d8ae91c6c50fabdac442ef8d6a68ae8c

  1. 對HTTP方法以及URI的摘要的合併值計算 MD5 雜湊值,例如,"GET" 和 "/index.html",結果稱為 HA2。

    HA2 = MD5( "GET:/" ) = 71998c64aea37ae77020c49c00f73fa8

  2. 最後生成的響應碼

    Response = MD5("d8ae91c6c50fabdac442ef8d6a68ae8c:L4qfzASytyQJAC2B1Lvy2llPpj9R8Jd3:00000001:c2dc5b32ad69187a
    :auth:71998c64aea37ae77020c49c00f73fa8") = 2f22e6d56dabb168702b8bb2d4e72453;

RFC2617 的安全增強的主要方式:

發起請求的時候,伺服器會生成一個密碼隨機數(nonce)(而這個隨機數只有每次"401"相應後才會更新),為了防止攻擊者可以簡單的使用同樣的認證資訊發起老的請求,於是,在後續的請求中就有一個隨機數計數器(cnonce),而且每次請求必須必前一次使用的打.這樣,伺服器每次生成新的隨機數都會記錄下來,計數器增加.在RESPONSE 碼中我們可以看出計數器的值會導致不同的值,這樣就可以拒絕掉任何錯誤的請求.

請求樣例(服務端 qop 設定為"auth")

客戶端 無認證

1
2
GET / HTTP/1.1
Host: localhost

伺服器響應(qop 為 'auth')

1
2
3
HTTP/1.1 401 Authorization Required
Date: Thu, 13 Jun 2013 20:25:37 GMT
WWW-Authenticate: Digest realm="Hi!", nonce="HSfb5dy15hKejXAbZ2VXjVbgNC8sC1Gq", qop="auth"

客戶端請求(使用者名稱: "tom", 密碼 "123456")

1
2
3
4
5
6
7
8
9
GET / HTTP/1.1
Host: localhost
Authorization: Digest username="tom",
                     realm="Hi!",
                     nonce="L4qfzASytyQJAC2B1Lvy2llPpj9R8Jd3",
                     uri="/",
                     qop=auth,
                     nc=00000001,
                     cnonce="c2dc5b32ad69187a",                     response="2f22e6d56dabb168702b8bb2d4e72453"

服務端應答

1
2
3
4
HTTP/1.1 200 OK
Date: Thu, 13 Jun 2013 20:25:37 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 53

注意qop 設定的時候慎用:auth-int,因為一些常用瀏覽器和服務端並沒有實現這個協議.

http協議的認證模式
http 的認證模式
  SIP類似Http協議。其認證模式也一樣。Http協議(RFC 2616 )規定可以採用Base模式和摘要模式(Digest schema)。RFC 2617 專門對兩種認證模式做了規定。RFC 1321 是MD5標準。Digest對現代密碼破解來說並不強壯,但比基本模式還是好很多。MD5已經被山東大學教授找到方法可以仿冒(我的理解),但現在還在廣泛使用。
1.最簡單的攻擊方式
  如果網站要求認證,客戶端傳送明文的使用者名稱密碼,那網路上的竊聽者可以輕而易舉的獲得使用者名稱密碼,起不到安全作用。我上學時曾在科大實驗室區域網內竊聽別人的科大BBS的密碼,發現BBS的使用者名稱密碼居然是明文傳輸的。那種做賊的心虛和做賊的興奮讓人激動莫名。偷人錢財會受到道德譴責,偷人密碼只會暗自得意忘形。比“竊書不算偷”還沒有罪惡感。因此你的使用者名稱和密碼明文傳輸的話,無異將一塊肥肉放在嘴饞的人面前。現在很多ASP網站的認證都將使用者名稱和密碼用MD5加密。MD5是將任意長度的字串和128位的隨機數字運算後生成一個16byte的加密字串。因此竊聽者抓住的是一團亂碼。但是,這有一個問題:如果竊聽者就用這團亂碼去認證,還是可以認證通過。因為伺服器將使用者名稱密碼MD5加密後得到的字串就是那一團亂碼,自然不能區別誰是合法使用者。這叫重放攻擊(replay attack)。這和HTTP的基本認證模式差不多。為了安全,不要讓別人不勞而獲,自然要做基本的防範。下面是Http協議規定的兩種認證模式。
2.基本認證模式
  客戶向伺服器傳送請求,伺服器返回401(未授權),要求認證。401訊息的頭裡面帶了挑戰資訊。realm用以區分要不同認證的部分。客戶端收到401後,將使用者名稱密碼和挑戰資訊用BASE64加密形成證書,傳送回伺服器認證。語法如下:
      challenge   = "Basic" realm
      credentials = "Basic" basic-credentials
示例:
   認證頭: WWW-Authenticate: Basic realm="[email protected]"
   證書:Authorization: Basic QsdfgWGHffuIcaNlc2FtZQ==
3.摘要訪問認證
  為了防止重放攻擊,採用摘要訪問認證。在客戶傳送請求後,收到一個401(未授權)訊息,包含一個Challenge。訊息裡面有一個唯一的字串:nonce,每次請求都不一樣。客戶將使用者名稱密碼和401訊息返回的挑戰一起加密後傳給伺服器。這樣即使有竊聽,他也無法通過每次認證,不能重放攻擊。Http並不是一個安全的協議。其內容都是明文傳輸。因此不要指望Http有多安全。
語法:
      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:讓客戶知道使用哪個使用者名稱和密碼的字串。不同的領域可能密碼不一樣。至少告訴使用者是什麼主機做認證,他可能會提示用哪個使用者名稱登入,類似一個Email。
domain:一個URI列表,指示要保護的域。可能是一個列表。提示使用者這些URI採用一樣的認證。如果為空或忽略則為整個伺服器。
nonce:隨機字串,每次401都不一樣。跟演算法有關。演算法類似Base64加密:time-stamp H(time-stamp ":" ETag ":" private-key) 。time-stamp為伺服器時鐘,ETag為請求的Etag頭。private-key為伺服器知道的一個值。
opaque:伺服器產生的由客戶下去請求時原樣返回。最好是Base64串或十六進位制字串。
auth-param:為擴充套件用的,現階段忽略。
其他域請參考RFC2617。
授權頭語法:
       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"
response:加密後的密碼
digest-uri:拷貝Request-Line,用於Proxy
cnonce:如果qop設定,才設定,用於雙向認證,防止攻擊。
nonce-count:如果伺服器看到同樣的計數,就是一次重放。
示例:
401響應:        HTTP/1.1 401 Unauthorized
         WWW-Authenticate: Digest
                 realm="[email protected]",
                 qop="auth,auth-int",
                 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
                 opaque="5ccc069c403ebaf9f0171e9517f40e41"
再次請求:
         Authorization: Digest username="Mufasa",
                 realm="[email protected]",
                 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
                 uri="/dir/index.html",
                 qop=auth,
                 nc=00000001,
                 cnonce="0a4f113b",
                 response="6629fae49393a05397450978507c4ef1",
                 opaque="5ccc069c403ebaf9f0171e9517f40e41"
4.比較基本認證和摘要訪問認證都是很脆弱的。基本認證可以讓竊聽者直接獲得使用者名稱和密碼,而摘要訪問認證竊聽者只能獲得一次請求的文件。