PHP网站中保持登录状态的功能是怎么做的?
11 个回答
我来谈谈这个问题吧
首先,基于以上几位朋友提到的,SESSION信息存储在服务端,相对于存储在客户端的COOKIE更为安全,所以正常一般网站在用于“判断用户是否登录”时,确实是使用SESSION,例如可以在SESSION里存储如下一个数组
//验证用户名和密码成功后
$_SESSION['userinfo'] = [
'uid' => 123,
'username' => 'testuser'
];
而后在需要验证登录的地方加入类似如下判断
if(empty($_SESSION['userinfo']) || empty($_SESSION['userinfo']['uid'])){
//未登录,引导登录
}
以上是使用SESSION做用户登录的基本存储和验证逻辑,当然实际开发过程中会将这部分的代码封装
我们都知道,SESSION一般是通过在COOKIE里记录一个KEY为PHPSESSID的COOKIE来保持上下文的,而这个PHPSESSID的COOKIE的有效期是设置为“会话”,意味着关闭浏览器后该COOKIE被销毁,相应的后端SESSION也就销毁,如图
另外,由于PHP的Session本身就有GC的机制,一般默认1440秒内页面没有刷新动作(准确的说是没有新的请求来刷新该PHPSESSID的生命周期),该SESSION也就被自动回收,伴随着用户的登录就失效了。
而题主关注的是如何实现这个“记住我”的功能,首先,虽然COOKIE保存在客户端,不安全,易被伪造,这是客观存在的事实,但要实现这个“记住我”,还确实就得用到COOKIE,我们能做的是尽量去提高伪造的门槛。
这边仅提供一个参考的设计方案,
1、将用户信息,比如一个['uid'=>123, 'username'=>'testuser']的数组,序列化后成为字符串,使用可逆加密算法加密该字符串,写到一个Key为userinfo的COOKIE里
2、由于可逆加密算法容易被解密,一旦加密的规则被别人猜测到以后,就可以轻易篡改这个COOKIE的内容,然后自行根据加密规则加密后伪造,所以,我们另外加入一个infodig的COOKIE,是将以上的userinfo的COOKIE内容,加入salt后使用不可逆加密算法生成散列,至于salt咱们可以自己定,总之要对外保密,不可逆算法例如md5,甚至多次加盐多次md5
3、以上两个COOKIE,为增强安全性,防止用户被XSS攻击后拿到,可以设置http-only属性
服务端判断存在以上两个COOKIE后
1、验证infodig与userinfo是否匹配(将userinfo的内容使用生成infodig的方法计算后,与COOKIE传上来的infodig匹配是否一致)
2、infodig验证通过后,使用解密算法解密userinfo串,得到用户信息,如果用户信息里的uid存在用户表中,则写SESSION,通过SESSION保持本次会话
总而言之,使用COOKIE记录用户信息是可行的(当然不建议把用户敏感的东西存在COOKIE,例如邮箱、手机、甚至密码,只记录对登录有用的部分,例如uid、username等标识,以及nickname可能会在某些地方提升用户体验),可以确定的是,这个COOKIE对用户可见,我们要做的就是两点:1、尽量让用户看不懂,而只有我们服务端自己认识(可逆加密算法); 2、即使用户看懂了,他也不能够轻易的伪造(不可逆的散列算法)
先说说结论:
1.cookie和session都可以用来保持登录状态。
2.如果使用cookie,用于保存用户状态的cookie需要加密,并且可被识别,但不必须可以解密。这个意思就是说,加密后的存储了用户登录信息的cookie数据在服务端可被还原,即使不能还原也要可以用于识别和比对(即唯一),php的加密函数可以去网上找,一搜一大把。
3.如果使用session,在不考虑安全的情况下,可以简单的使用$_SESSION这个全局数组达到目的,但是如果需要更高的安全性,需要额外的参数进行安全性验证,可以是加密字符串也可以是某串数据的MD5指纹。
4.使用session可以保证登录数据的一致性,如果登录数据中有在登录时间内可能会发生变动的项目,但是依然需要用到额外的参数去提取原来的session数据。
5.cookie比起session会有个滞后性,这一点需要注意,有时候有益,有时候也不方便。
6.在完全理解session的工作机制后,可以尝试抛弃php本身的session机制,建立自定义session机制。
终极结论:
为了保证安全性,既然都需要加密cookie,那么,为什么不把用户登录数据都存储在cookie里呢?session还会浪费服务器端存储。
终极进阶结论:
当你学会使用内存数据库的时候,比如memcached或者redis之类的,cookie和它们结合才是绝佳的解决方案。
下面扯点别的:
说起状态维持这件事情,一直是http的痛处,因为http是无状态协议,要让无状态协议支持状态维持,于是产生了cookie,早期浏览器对cookie戒心甚大,于是勉为其难用一下GET参数也是无妨,现代浏览器都是默认开启cookie的,甚至大部分都已经支持了浏览器端存储(storage)以及少部分支持了浏览器端数据库,后两者仅是存储,在请求时并不会向服务器反馈数据。而session是建立在cookie或者其他从浏览器传回的数据之上的,脱离了cookie或者其他从浏览器传回的参数(比如GET参数)就是无源之水、无根之木。
有人说,甚至有不少人说,session比cookie安全,我觉得这是不恰当的看法。cookie和session具有同等的安全性,并不会因为cookie存储于客户端而session存储于服务端而使cookie的安全性低于session,可以这么说,cookie和session的安全性都取决于cookie或者前文提到的传回服务器的参数的安全性。
而且session也不仅仅是局限于php本身提供的session库,任何存储于服务器端的数据都有可能扮演session的角色,本质上session是一种特殊的数据库,只不过这个数据库只允许已被识别的唯一合法客户端去访问。
虽然我在上面一段文字中强调了唯一,但是识别过程并不可靠,也就是有可能不唯一,这就是所谓的不安全性的来源。事实上,自从网络诞生以来,其上一直充斥的欺骗与暴力。这世上没有任何事情是唯一的,就像没有任何事物是完全相同的,虽然哲学一直教导我们世界上没有两件完全一样的事物,也没有两件完全独立的事物,但是这种绝对的观念并不适合相对的生活。生活中,存在唯一这个概念,然而令人遗憾的是并没有唯一的事实。即使有个枕边人温言细语的告诉你,你是他的唯一,如果你真信了,那只能是你太天真。唯一这个事情是有分辨粒度的,在可被识别的范围内,唯一才是有意义的,但是这种有意义却着实令人心碎,可识别意味着判断一个事物是否唯一的规则是有限的,有限就意味着可仿冒,时至今日,人类创造了各种用于识别唯一性的技术,但是关于识别技术,却依然在发展,而且永无止境。
最后的最后,附赠几段cookie操作函数:
特性1:去除了cookie的延时性,即设即用。
特性2:可以很方便的在cookie中存储数组,不需要一条条的写setcookie语句。
特性3:支持“.”索引,比如test.aaaa.bbbb是指cookie里的test[aaaa][bbbb]。
注意1:千万小心cookie的4kb限制,虽然有的现代浏览器可能扩充了这个限制。
注意2:cookie里存太多东西毕竟增加了网络数据传输以及服务器的数据处理。
function set_cookie($data,$path = '/',$time = 0,$doma = NULL)
{
if(!is_array($data))
{
$para = func_get_args();
$data = array($para[0] => $para[1]);
$path = isset($para[2]) ? $para[2] : '/';
$time = isset($para[3]) ? $para[3] : 0;
$doma = isset($para[4]) ? $para[4] : NULL;
}
if(is_int($path))
{
$time = $path;
$path = '/';
}
if(is_string($time))
{
$doma = $time;
$time = 0;
}
$time = $time == 0 ? $time : time()+$time;
foreach($data as $key => $value)
{
if(!is_array($value))
{
if(strpos($key,'.') === FALSE)
{
if(isset($_COOKIE[$key]))
{
if(is_array($_COOKIE[$key]))
{
$cookie_str = http_build_query(array($key => $_COOKIE[$key]));
$cookie_arr = explode('&',$cookie_str);
foreach($cookie_arr as $cookie)
{
$a_cookie = explode('=',$cookie);
setcookie(urldecode($a_cookie[0]),NULL,-1,$path,$doma);
}
$_COOKIE[$key] = NULL;
}
}
setcookie($key,$value,$time,$path,$doma);
$_COOKIE[$key] = $value;
}
else
{
$cop = &$_COOKIE;
$cox = substr_count($key,'.');
foreach(explode('.',$key) as $ckk => $ckey)
{
if($ckk > 0)
{
$cookie_key .= '['.$ckey.']';
}
else
{
$cookie_key = $ckey;
}
if($ckk < $cox)
{
if(isset($cop[$ckey]))
{
if(!is_array($cop[$ckey]))
{
setcookie($cookie_key,NULL,-1,$path,$doma);
$cop[$ckey] = NULL;
}
}
else
{
$cop[$ckey] = NULL;
}
$cop = &$cop[$ckey];
}
else
{
if(isset($cop[$ckey]))
{
if(is_array($cop[$ckey]))
{
$cookie_str = http_build_query(array($cookie_key => $cop[$ckey]));
$cookie_arr = explode('&',$cookie_str);
foreach($cookie_arr as $cookie)
{
$a_cookie = explode('=',$cookie);
setcookie(urldecode($a_cookie[0]),NULL,-1,$path,$doma);
}
$cop[$ckey] = NULL;
}
}
else
{
$cop[$ckey] = NULL;
}
$cop = &$cop[$ckey];
}
}
setcookie($cookie_key,$value,$time,$path,$doma);
$cop = $value;
}
}
else
{
$x_cookie_str = http_build_query($value);
$x_cookie_arr = explode('&',$x_cookie_str);
foreach($x_cookie_arr as $x_cookie)
{
$a_cookie = explode('=',$x_cookie);
if(isset($a_cookie[1]))
{
set_cookie($key.'.'.str_replace(array('[',']'),array('.',''),urldecode($a_cookie[0])),urldecode($a_cookie[1]),$time,$path,$doma);
}
}
}
}
}
function cookie($key = NULL,$def = FALSE)
{
if(!empty($key))
{
if(strpos($key,'.') === FALSE)
{
if(isset($_COOKIE[$key]))
{
return $_COOKIE[$key];
}
else
{
return $def;
}
}
else
{
$cop = &$_COOKIE;
foreach(explode('.',$key) as $ckey)
{
if(isset($cop[$ckey]))
{
$cop = &$cop[$ckey];
}
else
{
return $def;
}
}
return $cop;
}
}
else
{
return $_COOKIE;
}
}
function unset_cookie($key = NULL,$path = '/',$doma = NULL)
{
if(!empty($key))
{
if(strpos($key,'.') === FALSE)
{
if(isset($_COOKIE[$key]))
{
if(!is_array($_COOKIE[$key]))
{
setcookie($key,NULL,-1,$path,$doma);
}
else
{
$cookie_str = http_build_query(array($key => $_COOKIE[$key]));
$cookie_arr = explode('&',$cookie_str);
foreach($cookie_arr as $cookie)
{
$a_cookie = explode('=',$cookie);
setcookie(urldecode($a_cookie[0]),NULL,-1,$path,$doma);
}
}
unset($_COOKIE[$key]);
}
}
else
{
$cop = &$_COOKIE;
$ckeys = explode('.',$key);
$pop_ckey = array_pop($ckeys);
foreach($ckeys as $ckk => $ckey)
{
if($ckk > 0)
{
$cookie_key .= '['.$ckey.']';
}
else
{
$cookie_key = $ckey;
}
if(isset($cop[$ckey]))
{
$cop = &$cop[$ckey];
}
else
{
return;
}
}
if(isset($cop[$pop_ckey]))
{
if(!is_array($cop[$pop_ckey]))
{
setcookie($cookie_key.'['.$pop_ckey.']',NULL,-1,$path,$doma);
}
else
{
$cookie_str = http_build_query(array($cookie_key.'['.$pop_ckey.']' => $cop[$pop_ckey]));
$cookie_arr = explode('&',$cookie_str);
foreach($cookie_arr as $cookie)
{
$a_cookie = explode('=',$cookie);
setcookie(urldecode($a_cookie[0]),NULL,-1,$path,$doma);
}
}
unset($cop[$pop_ckey]);
}
}
}
else
{
if(!empty($_COOKIE))
{
$cookie_str = http_build_query($_COOKIE);
$cookie_arr = explode('&',$cookie_str);
foreach($cookie_arr as $cookie)
{
$a_cookie = explode('=',$cookie);
setcookie(urldecode($a_cookie[0]),NULL,-1,$path,$doma);
}
$_COOKIE = array();
}
}
}