用 PHP 實現 HTTP 身份驗證

HTTP 身份驗證 (HTTP authentication) 是一種十分常用而容易實作的驗證方法,它倚賴網頁伺服器的內置功能,大量縮短所需編寫的程式碼,對於用戶驗證的要求不高的系統,是一個很實用的驗證方法。Evert Pot 在他的網誌上討論了如何用 PHP 實作這種用戶驗證。

基本驗證 (Basic Auth)

HTTP 身份驗證有兩個主要的驗證方案:「基本驗證 (basic authentication)」和「摘要驗證 (digest authentication)」,其中基本驗證比較容易實作,所以也比較常見,以下是一個以 PHP 實作的基本驗證:

 HTML |  copy code |? 
01
<?php
02
 
03
$username = null;
04
$password = null;
05
 
06
// 若果有 mod_php
07
if (isset($_SERVER['PHP_AUTH_USER'])) {
08
    $username = $_SERVER['PHP_AUTH_USER'];
09
    $password = $_SERVER['PHP_AUTH_PW'];
10
 
11
// 其他伺服器
12
} elseif (isset($_SERVER['HTTP_AUTHENTICATION'])) {
13
    if (strpos(strtolower($_SERVER['HTTP_AUTHENTICATION']),'basic')===0) 
14
    list($username,$password) = explode(':',base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
15
}
16
 
17
if (is_null($username)) {
18
    header('WWW&minus;Authenticate: Basic realm="My Realm"');
19
    header('HTTP/1.0 401 Unauthorized');
20
    echo 'Text to send if user hits Cancel button';
21
    die();
22
} else {
23
    echo "<p>Hello {$username}.</p>";
24
    echo "<p>You entered {$password} as your password.</p>";
25
}
26
 
27
?>

這不算很複雜,是嗎?請注意用戶名稱和密碼都是用 base64 編碼後傳送到伺服器,除非你的伺服器使用 SSL,否則這不算十分安全。

摘要驗證 (Digest Auth)

摘要驗證旨在使驗證的過程更安全,驗證的過程中密碼永遠不會以明碼方式傳送,它以一個散列的形式被送到伺服器,散列的好處是它不能被還原為明碼,我 們只能以相同的散列函式來計算儲存在伺服器上的密碼,透過比較兩個散列值來進行驗證,若過兩者相同便表示密碼正確。我們首先看看摘要驗證如何運作:

客戶端請求的 URL:

GET / HTTP/1.1 

伺服器要求身份驗證:

HTTP/1.1 401 Unauthorized
WWW−Authenticate: Digest realm="The batcave",
qop="auth",
nonce="4993927ba6279",
opaque="d8ea7aa61a1693024c4cc3a516f49b3c"

HTTP/1.1 401 Unauthorized
WWW−Authenticate: Digest realm="The batcave",
qop="auth",
nonce="4993927ba6279",
opaque="d8ea7aa61a1693024c4cc3a516f49b3c"

客戶端驗證:

GET / HTTP/1.1
Authorization: Digest username="admin",
realm="The batcave",
nonce=49938e61ccaa4,
uri="/",
response="98ccab4542f284c00a79b5957baaff23",
opaque="d8ea7aa61a1693024c4cc3a516f49b3c",
qop=auth, nc=00000001,
cnonce="8d1b34edb475994b" 

來自伺服器的訊息:

realm一個將會用於使用者介面和散列函式的字串。
qop它的全寫是 quality of protection,可以是 auth 或 auth-int,它決定散列函式的計算方法,這裡我們使用 auth。
nonce一個獨特的代碼,這將會用於散列函式,用戶端需要發回這個代碼。
opaque這個可以視為會話 id,改變它的值會使瀏覽器註銷現有用戶的驗證。

來自客戶端的訊息:

username提供的用戶名稱。
realm與伺服器提供的 realm 相同。
nonce與伺服器提供的 nonce 相同。
uri用戶請求的 uri。
response驗證散列值。
opaque與伺服器提供的 opaque 相同。
qop與伺服器提供的 qop 相同。
nc一個十六進位的計數,每次客戶端送來一個請求這個數值便會遞增。
cnonce一個由客戶端產生的 id。

我們如何檢查密碼是否正確呢?以下的算法可以用來進行驗證:

4fb7b9011939a2_

用 PHP 來寫便是這樣:
 PHP |  copy code |? 
1
A1 = md5(username:realm:password)
2
A2 = md5(request-method:uri) // request method = GET, POST, etc.
3
Hash = md5(A1:nonce:nc:cnonce:qop:A2)
4
if (Hash == response)
5
//success!
6
else
7
//failure!

正如你看到,我們需要明碼版本的用戶密碼來計算散列值,但是從系統安全的角度,把用戶的密碼儲存在伺服器並非好主意,所以強烈推薦儲存 $A1 的值。

安全改進

  • 每次客戶端送來請求的時候,最好順便檢查 opaque、nonce 和 realm 的值,若果伺服器儲存了這些資料,為甚麼不檢查?
  • nc 的數值應該越來越大,建議在伺服器儲存這個數值,並確保它沒有任何突然大的跳躍,它未必會嚴格的順序遞增,由於網絡的性質,你收到的數值偶然會缺少一個,或者沒有按順序。
  • qop 是一個質量參數,如果 qop 設定為 auth,只有請求的 uri 會被散列函式使用,如果 qop 是 auth-int,請求的內容亦會被散列函式使用。(A2 = md5(request-method:uri:md5(request-body))

註銷驗證

伺服器只要再次送出 HTTP 401,瀏覽器便會註銷先前的驗證資料,從新要求用戶驗證。

發表回覆

  

  

  

您可使用下列 these HTML標籤

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>