PHP用COOKIE實現一套SESSION機制

PHP的 SESSION是根據訪客瀏覽器傳過來的SESSION ID(表現爲一個COOKIE)
來找到服務器的會話文件(/tmp/SESS_ID),以便經過$_SESSION數組讀取裏面的內容(會話變量).
利用PHP的SESSION能夠很方便地實現購物車,驗證碼,csrf_token的記錄.
能夠把一個關聯數組賦值給一個會話變量.

兩種方式的比較 :
COOKIE(PHPSESSID) -> /tmp/SESS_XXX -> $_SESSION數組
COOKIE(MYID) -> 查詢數據庫進行身份驗證 -> online表(session字段)

密碼入庫處理:
$salt = sha1(uniqid(mt_rand(), true)); //mt_rand()表示前綴,true表示在末尾加上熵.
$pwd_db = sha1($salt.sha1($pwd_user)); //PHP官方建議使用password_hash取代sha1這些哈希"加密"
其中隨機生成的鹽值$salt和加鹽散列後的密碼$pwd_db都存儲到用戶表對應的用戶記錄裏.
驗證密碼時,根據公式算出用戶輸入的密碼,而後進行字符串比對(==或===或strcmp).
這樣就算數據庫裏的$pwd_db泄露了,攻擊者也很難經過彩虹表查到密碼的明文.
其中:
uniqid獲取一個帶前綴,基於當前時間微秒數的惟一ID.
mt_rand生成更好的隨機數.

COOKIE裏存儲的密碼:
$pwd_cookie = sha1($global_salt.sha1($pwd_db));
$pwd_cookie = mcrypt_blowfish(sha1($global_salt.sha1($pwd_db)), $key);
HTTP下,截獲沒有加密的cookie,破解哈希後就能拿到$pwd_db.
因此最好仍是用MCRYPT_BLOWFISH加密cookie.
驗證COOKIE時,根據COOKIE裏的用戶ID查詢用戶密碼,計算後比對兩個字符串.
其中$global_salt是在config.php定義的系統全局鹽,一旦修改,全部COOKIES將會失效.

生成COOKIE:
COOKIE設計要實現身份驗證,避免COOKIE被僞造和用戶的密碼明文被破解.
setcookie($cookie_name, $value);
$value = base64_encode($user_id.'|'.$pwd_cookie);

解析COOKIE:
解析瀏覽器發過來的COOKIE得到用戶ID,密碼等信息,通過認證後即可讀寫online表的會話變量.
base64_decode -> explode -> 密碼驗證
根據用戶ID在online表中查找記錄,若是沒有,則新增一條,有則能夠讀取上面的數據.
拿購物車來講,就算用戶在不一樣電腦不一樣瀏覽器上登陸,系統仍能查詢到用戶添加到購物車裏的內容.
而PHP SESSION則是不能夠的,由於每次登陸都會生成不一樣的會話文件(/tmp/SESS_XX1,/tmp/SESS_XX2).

存儲會話變量:
在MySQL建一個內存表online,用一條記錄標記一個cookie,創建字段存儲會話變量.
好比要實現購物車,就創建一個購物車字段,裏面能夠存儲通過serialize序列化成串的商品數組.
你甚至能夠建一個字段session,用於存儲全部會話變量,
把這個字段當成SESSION裏的會話文件用,從這個字段unserialize反序列化獲得的數組就至關於$_SESSION了.
CREATE TABLE IF NOT EXISTS `online` (
  `user_id` int(10) unsigned NOT NULL,
  `session` varchar(20480) NOT NULL DEFAULT '',
  PRIMARY KEY ( `user_id` )
) ENGINE=MEMORY DEFAULT CHARSET=utf8;
20480個字符的會話存儲容量應該仍是夠用的.

減小每次COOKIE認證都要查詢 數據庫 的損耗:
爲了不每次都查詢數據庫驗證COOKIE,能夠考慮結合SESSION設一個會話變量記錄登陸狀態.
或者考慮把$user_id和$pwd_db存到Redis中,驗證COOKIE時直接從Redis根據$user_id哈希找到$pwd_db,計算後比對.
$sess_1 = array('pwd_db'=>$pwd_db, 'name'=>'Joe', 'role'=>'student');
$redis->hSet('sessions', 'user_1', json_encode($sess_1));
print_r( json_decode($redis->hGet('sessions', 'user_1'), true) );
用Memcached或者 Yac 也是能夠的,SESS_UID設爲key,json編碼的串做爲value,$pwd_db是json串裏的一個成員.
這裏提一下,Discuz!採用COOKIE加密後查詢MySQL數據庫進行身份驗證.
對比:
Redis/Memcached/Yac: key(用戶ID) value(用戶密碼+會話變量數組)
MySQL: 主鍵(用戶ID) 字段(會話變量數組)

後記:
密碼散列相關函數: password_hash(推薦) crypt(blowfish) hash sha1 md5
PHP官方不推薦使用sha1對密碼進行"加密"(散列),而是建議使用PHP原生實現的密碼散列函數 password_hash.
password_hash是從5.5開始引入的,但有一個 用PHP實現的兼容庫,在PHP版本不支持password_hash時,改用crypt實現.
具體請看官方這篇文章《 PHP密碼散列安全 》。php

相關文章
相關標籤/搜索