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));
$pwd_db = sha1($salt.sha1($pwd_user));
其中随机生成的盐值$salt和加盐散列后的密码$pwd_db都存储到用户表对应的用户记录里.
验证密码时,根据公式算出用户输入的密码,然后进行字符串比对(==或===或strcmp).
这样就算数据库里的$pwd_db泄露了,攻击者也很难通过彩虹表查到密码的明文.
其中:
uniqid获取一个带前缀,基于当前时间微秒数的唯一ID.
mt_rand生成更好的随机数.

COOKIE里存储的密码:
$pwd_cookie = sha1($global_salt.sha1($pwd_db));
验证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用COOKIE实现一套SESSION机制)