修改织梦网站管理系统PHP程序,实现禁止同一会员帐号多地登录,一般来讲就要从判断IP入手。我修改的这个原理是:会员登录后增加创建名为Only的cookie,当会员刷新页或浏览新页面时判断从数据表@_member中当前会员的loginip值md5后与Only比较异同,把此条件加在验证用户是否已经登录函数IsLogin()中,成立返回真,不成立返回假,即可实现。修改如下:
修改文件/include/memberlogin.class.php
构造函数,大约171行左右的$this->OnlyCookie = GetCookie("Only");
代码下一行增加代码如下:
$this->OnlyCookie = GetCookie("Only");
验证用户是否已经登录函数IsLogin(),大约第290行 改为
function IsLogin()
{
$loginipCookie = substr(md5($this->fields['loginip']),0,16);
if($this->M_ID > 0 && $this->OnlyCookie == $loginipCookie) return TRUE;
else return FALSE;
}
重置用户信息函数ResetUser()内的最后,大约第389行左右代码DropCookie('DedeLoginTime');
的下一行增加代码如下(除管理员外的cookie方法):
DropCookie('Only');
意为重置会员cookie信息
大约第530行左右保存用户cookie的函数PutLoginInfo()内的if($this->M_KeepTime > 0)内增加代码如下(除管理员外的cookie方法);
PutCookie('Only',substr(md5(GetIp()),0,16),$this->M_KeepTime);
意为增加cookie条目
下面的else内最后加上
PutCookie('Only',$this->M_LoginTime);
修改文件/include/userlogin.class.php(这个没有测试管理员的帐号效果,后来我没我修改这条,也就是说这条改不改不影响除ID为1的其他会员的禁止多地登录效果)
keepUser()函数内大约第315行增加代码如下:
PutCookie('Only', substr(md5('MrDede'.GetIp()),0,16), 3600 * 24, '/');
保持用户的会话状态,这里给管理员(id=1)增加了名为Only的cookie信息
————————–
2020-02-26 修改
2021-02-01 再次修改
因为之前修改时的说明有些笼统,说明中隐含的借用了上面的说明,逻辑也有一些问题,造成使用此方法修改的朋友不能实现单点登录的功能,在这里表示抱歉。
这次(2021-02-01)有朋友来问我为什么不能实现单点登录功能,看自己的说明,实测了一下,确实。所以,又进行了回忆、修改、添加详细说明。现在实测可用。
当时可能是因为任务匆忙,没有写详细说明,只是在心里想着,没有写出来,造成现在自己看自己的说明都看不懂。这也是对自己的一个教训,以后一定要写详细的说明。
以下修改方法与以上方法无关
————————–
有朋友看到文章来找我帮着修改单点登录的功能,上面是过去的思路,随着时间的变化,对此功能的实现也有了新的想法。
因为现在的路由多是动态路由,所以靠IP来识别,就显示不严谨、不可靠了。另外,还要看用户的需求,一种需求是在同一IP下可以多设备登录,另一种需求就是即使在同一IP下也要实现单个设备登录。
新的思路(单设备登录):
1、数据表dede_member新建两个字段,token,token_time
0 1 2 |
ALTER TABLE `dede_member` ADD COLUMN `token` varchar(120) NOT NULL DEFAULT '' COMMENT 'Token字符串' AFTER `checkmail`, ADD COLUMN `token_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Token创建时间' AFTER `token`; |
上面SQL语句中的checkmail是表dede_member的最后一个字段,当然新字段也可以接在别的字段后,这个随自己的意。
2、在/include/memberlogin.class.php的MemberLogin类内部的最后增加方法:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
/** * 单点登录更新token - 织梦先生 添加 * 注:在确保按原有逻辑登录成功后,使用此方法 * @param boolean $set 是否直接设置 token,登录时$set=true,非登录时不会设置此参数 * @return boolean */ function resetToken($set = false) { global $dsql; $token_time = time(); if(!empty($set)) { // 登录时,直接设置 Cookie // 强制生成一个随机数做为新 token,并和当前时间组合加密 $token = rand(1000, 9999); $newEncode = sha1($token . $token_time); // 更新会员表 token_time 字段 $dsql->ExecuteNoneQuery("UPDATE `#@__member` SET token='$token',token_time='$token_time' WHERE mid='{$this->M_ID}' "); // 更新前端 Cookie 中的 DedeToken 值 if($this->M_KeepTime > 0) { PutCookie('DedeToken',$newEncode,$this->M_KeepTime); } else { PutCookie('DedeToken',$newEncode); } $this->isToken = true; $this->fields['token_time'] = $token_time; return $this->isToken; } if(!empty($this->fields) && empty($this->fields['token'])) { // 这个逻辑只会进入一次,也就当会员表字段token为空的时间, // 理论上讲,只要会员登录一次,这个字段就永远不会为空,除非人为的修改 // 所以才说,这个逻辑只会进入一次。 $token = 'Genesis'; // 创世 $encode = sha1($token . $token_time); // 与时间组合加密 // 更新会员的 token 字段和与token组合的时间字段 token_time $dsql->ExecuteNoneQuery("UPDATE `#@__member` SET token='$token',token_time='$token_time' WHERE mid='{$this->M_ID}' "); // 织梦系统是否设置了Cookie过期时间,如果设置了的话,在生成cookie的时候要带上 if($this->M_KeepTime > 0) { PutCookie('DedeToken',$encode,$this->M_KeepTime); } else { PutCookie('DedeToken',$encode); } $this->fields['token_time'] = $token_time; $this->isToken = false; } else { // 每次访问都会进入这个逻辑 // 获取前端保存的 token $tokenCookie = GetCookie('DedeToken'); if(empty($tokenCookie)) { $this->isToken = false; } else { // 组合 token 和 token_time,并加密 $encode = sha1($this->fields['token'] . $this->fields['token_time']); // 与前端传递的加密token对比 $this->isToken = $tokenCookie === $encode; if($this->isToken) { // 验证通过 // 保存 cookie 的 DedeToken 值到token,并更新token_time字段为当前时间戳 $dsql->ExecuteNoneQuery("UPDATE `#@__member` SET token='$tokenCookie',token_time='$token_time' WHERE mid='{$this->M_ID}' "); // 生成新的准备下发到cookie DedeToken的加密值 $newEncode = sha1($tokenCookie . $token_time); if($this->M_KeepTime > 0) { PutCookie('DedeToken',$newEncode,$this->M_KeepTime); } else { PutCookie('DedeToken',$newEncode); } } } } return $this->isToken; } |
3、类中增加成员变量 var $isToken = false;
4、在构造方法内引用resetToken()方法,一定要放在构造方法内的最后引用。
为了尽可能是符合织梦系统的逻辑,所以选择把此验证方法加在如下位置。在开发过程中,无论怎么修改,都要尽可能的保持原信息的逻辑关系。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
//php5构造函数 function __construct($kptime = -1, $cache=FALSE) { global $dsql; ...... if(empty($this->M_ID)) { $this->ResetUser(); }else{ $this->M_ID = intval($this->M_ID); ...... if(is_array($this->fields)){ ...... if( !$formcache ) { SetCache($this->memberCache, $this->M_ID, $this->fields, 1800); } $this->resetToken(); // 加在这里 }else{ $this->ResetUser(); } } } |
5、在PutLoginInfo()方法中引用resetToken()方法,如:
0 1 2 3 4 |
function PutLoginInfo($uid, $logintime=0) { global $cfg_login_adds, $dsql; $this->resetToken(); ...... |
6、修改ResetUser()方法
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/** * 重置用户信息 * * @return void */ function ResetUser() { $this->fields = ''; $this->M_ID = 0; $this->M_LoginID = ''; $this->M_Rank = 0; $this->M_Face = ""; $this->M_Money = 0; $this->M_UserName = ""; $this->M_LoginTime = 0; $this->M_MbType = ''; $this->M_Scores = 0; $this->M_Spacesta = -2; $this->M_UpTime = 0; $this->M_ExpTime = 0; $this->M_JoinTime = 0; $this->M_HasDay = 0; DropCookie('DedeUserID'); DropCookie('DedeLoginTime'); DropCookie('DedeToken'); // 添加这个 } |
7、/member/index_do.php中,在$cfg_ml->DelCache($cfg_ml->M_ID);
后添加引用$cfg_ml->resetToken(true);
如:
0 1 2 3 4 5 |
else { // 清除会员缓存 $cfg_ml->DelCache($cfg_ml->M_ID); $cfg_ml->resetToken(true); ...... |
8、修改登录验证的方法IsLogin(),修改如下:
0 1 2 3 4 5 6 7 8 9 |
/** * 验证用户是否已经登录 * * @return bool */ function IsLogin() { if($this->M_ID > 0 && $this->isToken) return TRUE; else return FALSE; } |
9、前端控制:后端创建接口,验证验证登录状态;前端轮询访问接口,编写自己的逻辑。后端控制:根据实际业务编写逻辑。
简单一点说这个逻辑,就是用户每次访问,都验证签名,验证通过后创建一个新签名,把签名生成条件保存入库,同时把签名写入Cookie,如此循环。。。