redis實踐:用戶注冊登錄功能


本節將使用PHP和Redis實現用戶注冊登錄功能,下面分模塊來介紹具體實現方法。

1.注冊

需求描述:用戶注冊時需要提交郵箱、登錄密碼和昵稱。其中郵箱是用戶的唯一標識,每個用戶的郵箱不能重復,但允許用戶修改自己的郵箱。

我們使用散列類型來存儲用戶的資料,鍵名為user:用戶ID。其中用戶ID是一個自增的數字,之所以使用 ID 而不是郵箱作為用戶的標識是因為考慮到在其他鍵中可能會通過用戶的標識與用戶對象相關聯,如果使用郵箱作為用戶的標識的話在用戶修改郵箱時就不得不同時需要修改大量的鍵名或鍵值。為了盡可能地減少要修改的地方,我們只把郵箱作為該散列鍵的一個字段。為此還需要使用一個散列類型的鍵email.to.id來記錄郵箱和用戶ID間的對應關系以便在登錄時能夠通過郵箱獲得用戶的ID。

用戶填寫並提交注冊表單后首先需要驗證用戶輸入,我們在項目目錄中建立一個register.php文件來實現用戶注冊的邏輯。驗證部分的代碼如下:

// 設置Content-type以使瀏覽器可以使用正確的編碼顯示提示信息,

// 具體的編碼需要根據文件實際編碼選擇,此處是utf-8。

header("Content-type: text/html; charset=utf-8");

if(!isset($_POST['email']) ||

   !isset($_POST['password']) ||

   !isset($_POST['nickname'])) {

    echo '請填寫完整的信息。';

   exit;

}

$email = $_POST['email'];

// 驗證用戶提交的郵箱是否正確

if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {

     echo '郵箱格式不正確,請重新檢查';

    exit;

}

$rawPassword = $_POST['password'];

// 驗證用戶提交的密碼是否安全

if(strlen($rawPassword) < 6) {

     echo '為了保證安全,密碼長度至少為6。';

    exit;

}

$nickname = $_POST['nickname'];

//不同的網站對用戶昵稱有不同的要求,這里不再做檢查,即使是空也可以。

// 而后我們需要判斷用戶提交的郵箱是否被注冊了:

$redis = new Predis\Client();

if($redis->hexists('email.to.id', $email)) {

     echo '該郵箱已經被注冊過了。';

    exit;

}

驗證通過后接下來就需要將用戶資料存入Redis中。在存儲的時候要記住使用散列函數處理用戶提交的密碼,避免在數據庫中存儲明文密碼。原因是如果數據庫中數據泄露(外部原因或內部原因都有可能),攻擊者也無法獲得用戶的真實密碼,也便無法正常地登錄進系統。更重要的是考慮到用戶很可能在其他網站中也使用了同樣的密碼,所以明文密碼泄露還會給用戶造成額外的損失。

除此之外,還要避免使用速度較快的散列函數處理密碼以防止攻擊者使用窮舉法破解密碼,並且需要為每個用戶生成一個隨機的“鹽”(salt)以避免攻擊者使用彩虹表破解。這里作為示例,我們使用Bcrypt算法來對密碼進行散列。PHP 5.3中提供的crypt函數支持Bcrypt算法,我們可以實現一個函數來隨機生成鹽並調用crypt函數獲得散列后的密碼:

function bcryptHash($rawPassword, $round = 8)

{      

    if ($round < 4 || $round > 31) $round = 8;

    $salt = '$2a$' . str_pad($round, 2, '0', STR_PAD_LEFT) . '$';

    $randomValue = openssl_random_pseudo_bytes(16);

    $salt .= substr(strtr(base64_encode($randomValue), '+', '.'),  0, 22);

    return crypt($rawPassword, $salt);

}

提示  openssl_random_pseudo_bytes函數需要安裝OpenSSL擴展。

之后使用如下代碼獲得散列后的密碼:

$hashedPassword = bcryptHash($rawPassword);

存儲用戶資料就很簡單了,所有命令都在第3章介紹過了。代碼如下:

require './predis/autoload.php';

$redis = new Predis\Client();

// 首先獲取一個自增的用戶ID

$userID = $redis->incr('users:count');

// 存儲用戶信息

$redis->hmset("user:{$userID}", array(

    'email'  => $email,

    'password'   => $hashedPassword,

    'nickname'   => $nickname

));

// 記得記錄下郵箱和用戶ID的對應關系

$redis->hset('email.to.id', $email, $userID);

// 提示用戶注冊成功

echo '注冊成功!';

大部分情況下在注冊時我們需要驗證用戶的郵箱,不過這部分的邏輯與忘記密碼部分相似,所以在這里不做更多的介紹。

2.登錄

需求描述:用戶登錄時需要提交郵箱和登錄密碼,如果正確則輸出“登錄成功”,否則輸出“用戶名或密碼錯誤”。

當用戶提交郵箱和登錄密碼后首先通過email.to.id鍵獲得用戶ID,然后將用戶提交的登錄密碼使用同樣的鹽進行散列並與數據庫存儲的密碼比對,如果一樣則表示登錄成功。我們新建一個login.php文件來處理用戶的登錄,處理該邏輯的部分代碼如下:

header("Content-type: text/html; charset=utf-8");

if(!isset($_POST['email']) ||

   !isset($_POST['password'])) {

   echo '請填寫完整的信息。';

   exit;

}

$email = $_POST['email'];

$rawPassword = $_POST['password'];

require './predis/autoload.php';

$redis = new Predis\Client();

// 獲得用戶的ID

$userID = $redis->hget('email.to.id', $email);

if(!$userID) {

     echo '用戶名或密碼錯誤。';

    exit;

}

$hashedPassword = $redis->hget("user:{$userID}", 'password');

現在我們得到了之前存儲過的經過散列后的密碼,接着定義一個函數來對用戶提交的密碼進行散列處理。bcryptHash函數中返回的密碼中已經包含了鹽,所以只需要直接將散列后的密碼作為crypt函數的第二個參數,crypt函數會自動地提取出密碼中的鹽:

function bcryptVerify($rawPassword, $storedHash)

{  

    return crypt($rawPassword, $storedHash) == $storedHash;

}

之后就可以使用此函數進行比對了:

if(!bcryptVerify($rawPassword, $hashedPassword)) {

     echo '用戶名或密碼錯誤。';

    exit;

}

echo '登錄成功!';

3.忘記密碼

需求描述:當用戶忘記密碼時可以輸入自己的郵箱,系統會發送一封包含更改密碼的鏈接的郵件,用戶單擊該鏈接后會進入密碼修改頁面。該模塊的訪問頻率限制為1分鍾10次以防止惡意用戶通過此模塊向某個郵箱地址大量發送垃圾郵件。

當用戶在忘記密碼的頁面輸入郵箱后,我們的程序需要做兩件事。

(1)進行訪問頻率限制。這里使用4.2.3節介紹的方法以郵箱為標示符對發送修改密碼郵件的過程進行訪問頻率限制。當用戶提交了郵箱地址后首先驗證郵箱地址是否正確,如果正確則檢查訪問頻率是否超限:

$keyName = "rate.limiting:{$email}";

$now = time();

 

if($redis->llen($keyName) < 10) {

    $redis->lpush($keyName, $now);

} else {

    $time = $redis->lindex($keyName, -1);

    if($now - $time < 60) {

          echo '訪問頻率超過了限制,請稍后再試。';

        exit;

    } else {

        $redis->lpush($keyName, $now);

        $redis->ltrim($keyName, 0, 9);

    }

}

一般在全站中還會有針對IP地址的訪問頻率限制,原理與此類似。

(2)發送修改密碼郵件。用戶通過訪問頻率限制后我們會為其生成一個隨機的驗證碼,並將驗證碼通過郵件發送給用戶。同時在程序中要把用戶的郵箱地址存入名為retrieve.password.code:散列后的驗證碼的字符串類型鍵中,然后使用EXPIRE命令為其設置一個生存時間(如1個小時)以提供安全性並且保證及時釋放存儲空間。由於忘記密碼需要的安全等級與用戶注冊登錄相同,所以我們依然使用Bcrypt算法來對驗證碼進行散列,具體的算法同上這里不再詳述。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM