2010-06-27 149 views
34

我想在登錄前添加一個「記住我」複選框選項PHP登錄系統:記住(永久cookie)

什麼是安全地存儲在用戶瀏覽器的cookie的最佳方式?

例如,Facebook上有自己的「記住我」複選框,這樣你每次進入facebook.com一次你在已經登錄。

我目前登錄使用簡單的會話。

+0

您可以查看https://github.com/delight-im/PHP-Auth及其來源,瞭解如何實現* secure *「記住我」功能。基本上,只需在cookie中存儲一些非常長的*(即很多熵)的隨機數據串。當用戶訪問您的頁面時,請在跟蹤這些標記的數據庫中檢查「標記」。如果令牌有效,則認證用戶。 – caw 2016-07-13 00:01:25

回答

34

這個問題被問到很多,這裏有一些鏈接給你。

還有在回答這個問題集中到一起一些重要的資源:The Definitive Guide To Website Authentication

+2

更好的解決方案:[在具有長期持久性的PHP應用程序中實現安全用戶身份驗證](https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence#title.2 )(2015) – 2015-05-11 05:15:40

32

更新(2017年8月13日):要理解爲什麼我們分離selectortoken,而不是僅僅使用token,請閱讀SELECT查詢this article about splitting tokens to prevent timing attacks

我要提取這篇博客about secure long-term authentication勾畫的戰略,因爲這涵蓋了大量的地面,我們只在"remember me"部分感興趣。

序言 - 數據庫結構

我們希望從我們的用戶表一個單獨的表看起來像這樣(MySQL的):

CREATE TABLE `auth_tokens` (
    `id` integer(11) not null UNSIGNED AUTO_INCREMENT, 
    `selector` char(12), 
    `token` char(64), 
    `userid` integer(11) not null UNSIGNED, 
    `expires` datetime, 
    PRIMARY KEY (`id`) 
); 

這裏最重要的事情是selectortoken是分開的領域。

記錄在

後如果沒有random_bytes()做什麼,只是搶random_compat副本。

if ($login->success && $login->rememberMe) { // However you implement it 
    $selector = base64_encode(random_bytes(9)); 
    $authenticator = random_bytes(33); 

    setcookie(
     'remember', 
     $selector.':'.base64_encode($authenticator), 
     time() + 864000, 
     '/', 
     'yourdomain.com', 
     true, // TLS-only 
     true // http-only 
    ); 

    $database->exec(
     "INSERT INTO auth_tokens (selector, token, userid, expires) VALUES (?, ?, ?, ?)", 
     [ 
      $selector, 
      hash('sha256', $authenticator), 
      $login->userId, 
      date('Y-m-d\TH:i:s', time() + 864000) 
     ] 
    ); 
} 

重新認證頁面加載

if (empty($_SESSION['userid']) && !empty($_COOKIE['remember'])) { 
    list($selector, $authenticator) = explode(':', $_COOKIE['remember']); 

    $row = $database->selectRow(
     "SELECT * FROM auth_tokens WHERE selector = ?", 
     [ 
      $selector 
     ] 
    ); 

    if (hash_equals($row['token'], hash('sha256', base64_decode($authenticator)))) { 
     $_SESSION['userid'] = $row['userid']; 
     // Then regenerate login token as above 
    } 
} 

詳細

我們用9個字節爲我們選擇隨機數據(編碼爲12個字符的base64)的。這提供密鑰空間的72位,因此2 抗衝突性(生日攻擊),其比爲16

一個因子我們的存儲容量(integer(11) UNSIGNED)放大我們使用的33個字節(264位)的位我們的實際認證者的隨機性。這在所有的實際情況下都是不可預測的。

我們將驗證器的SHA256哈希存儲在數據庫中。這可以緩解信息泄露後用戶冒充的風險。

我們重新計算存儲在用戶cookie中的驗證器值的SHA256哈希值,然後使用hash_equals()將其與存儲的SHA256哈希值進行比較以防止計時攻擊。

我們將選擇器與驗證器分開,因爲數據庫查找不是恆定時間。這消除了時間泄漏對搜索造成的潛在影響,而不會造成嚴重的性能下降。

+1

出色的工作。值得在代碼中添加到期檢查嗎?你是否在代碼的其他地方重新生成了令牌(例如,每10頁加載一次)? – rybo111 2015-06-13 10:19:17

+0

啊!這看起來不錯,但我無法使用以下任何自定義函數使'hash_equals'返回true:https://php.net/hash_equals#115664 – rybo111 2015-06-13 18:13:50

+0

如果您需要一個in-PHP實現:https:// github。 com/sarciszewski/php-future – 2015-06-14 19:26:39