入门客AI创业平台(我带你入门,你带我飞行)
博文笔记

DISCUZ 自动登录功能解析

创建时间:2014-04-24 投稿人: 浏览次数:3606

这里以DISCUZ X2.5 为例, X3和X2.5几乎一样

联系作者:addon.discuz.com/?@12744.developer     做企业网站:www.dianzana.com

选中自动登录,查看网页代码如下:

<form method="post" autocomplete="off" id="lsform" action="member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yes" onsubmit="return lsSubmit();">
<div class="fastlg cl">
<span id="return_ls" style="display:none"></span>
<div class="y pns">
<table cellspacing="0" cellpadding="0">
<tbody><tr>
<td>
<span class="ftid">
<select name="fastloginfield" id="ls_fastloginfield" width="40" tabindex="900" selecti="0" style="display: none;">


<option value="username"></option></select><a href="javascript:;" id="ls_fastloginfield_ctrl" style="width:40px" tabindex="900">用户名</a>
</span>
<script type="text/javascript">simulateSelect("ls_fastloginfield")</script>
</td>
<td><input type="text" name="username" id="ls_username" autocomplete="off" class="px vm" tabindex="901"></td>
<td class="fastlg_l"><label for="ls_cookietime"><input type="checkbox" name="cookietime" id="ls_cookietime" class="pc" value="2592000" tabindex="903">自动登录</label></td>
<td> <a href="javascript:;" onclick="showWindow("login", "member.php?mod=logging&action=login&viewlostpw=1")">找回密码</a></td>
</tr>
<tr>
<td><label for="ls_password" class="z psw_w">密码</label></td>
<td><input type="password" name="password" id="ls_password" class="px vm" autocomplete="off" tabindex="902"></td>
<td class="fastlg_l"><button type="submit" class="pn vm" tabindex="904" style="width: 75px;"><em>登录</em></button></td>
<td> <a href="member.php?mod=register" class="xi2 xw1">立即注册</a></td>
</tr>
</tbody></table>
<input type="hidden" name="quickforward" value="yes">
<input type="hidden" name="handlekey" value="ls">
</div>
</div>
</form>

很明显,访问的地址是:member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yes, 自动登录复选框的name="cookietime"

依访问路径找到文件:

x25/member.php

x25/source/module/member/member_logging.php  其中 new logging_ctl() 找到

x25/source/class/class_member.php  下面是on_login()方法

function on_login() {
		global $_G;
		if($_G["uid"]) {
			$referer = dreferer();
			$ucsynlogin = $this->setting["allowsynlogin"] ? uc_user_synlogin($_G["uid"]) : "";
			$param = array("username" => $_G["member"]["username"], "usergroup" => $_G["group"]["grouptitle"], "uid" => $_G["member"]["uid"]);
			showmessage("login_succeed", $referer ? $referer : "./", $param, array("showdialog" => 1, "locationtime" => true, "extrajs" => $ucsynlogin));
		}

		$from_connect = $this->setting["connect"]["allow"] && !empty($_GET["from"]) ? 1 : 0;
		$seccodecheck = $from_connect ? false : $this->setting["seccodestatus"] & 2;
		$seccodestatus = !empty($_GET["lssubmit"]) ? false : $seccodecheck;
		$invite = getinvite();

		if(!submitcheck("loginsubmit", 1, $seccodestatus)) {

			$auth = "";
			$username = !empty($_G["cookie"]["loginuser"]) ? dhtmlspecialchars($_G["cookie"]["loginuser"]) : "";

			if(!empty($_GET["auth"])) {
				list($username, $password, $questionexist) = explode("	", authcode($_GET["auth"], "DECODE"));
				$username = dhtmlspecialchars($username);
				$auth = dhtmlspecialchars($_GET["auth"]);
			}

			$cookietimecheck = !empty($_G["cookie"]["cookietime"]) || !empty($_GET["cookietime"]) ? "checked="checked"" : "";

			if($seccodecheck) {
				$seccode = random(6, 1) + $seccode{0} * 1000000;
			}

			if($this->extrafile && file_exists($this->extrafile)) {
				require_once $this->extrafile;
			}

			$navtitle = lang("core", "title_login");
			include template($this->template);

		} else {

			if(!empty($_GET["auth"])) {
				list($_GET["username"], $_GET["password"]) = daddslashes(explode("	", authcode($_GET["auth"], "DECODE")));
			}

			if(!($_G["member_loginperm"] = logincheck($_GET["username"]))) {
				showmessage("login_strike");
			}
			if($_GET["fastloginfield"]) {
				$_GET["loginfield"] = $_GET["fastloginfield"];
			}
			$_G["uid"] = $_G["member"]["uid"] = 0;
			$_G["username"] = $_G["member"]["username"] = $_G["member"]["password"] = "";
			if(!$_GET["password"] || $_GET["password"] != addslashes($_GET["password"])) {
				showmessage("profile_passwd_illegal");
			}
			$result = userlogin($_GET["username"], $_GET["password"], $_GET["questionid"], $_GET["answer"], $this->setting["autoidselect"] ? "auto" : $_GET["loginfield"], $_G["clientip"]);
			$uid = $result["ucresult"]["uid"];

			if(!empty($_GET["lssubmit"]) && ($result["ucresult"]["uid"] == -3 || $seccodecheck)) {
				$_GET["username"] = $result["ucresult"]["username"];
				$this->logging_more($result["ucresult"]["uid"] == -3);
			}

			if($result["status"] == -1) {
				if(!$this->setting["fastactivation"]) {
					$auth = authcode($result["ucresult"]["username"]."	".FORMHASH, "ENCODE");
					showmessage("location_activation", "member.php?mod=".$this->setting["regname"]."&action=activation&auth=".rawurlencode($auth)."&referer=".rawurlencode(dreferer()), array(), array("location" => true));
				} else {
					$init_arr = explode(",", $this->setting["initcredits"]);
					$groupid = $this->setting["regverify"] ? 8 : $this->setting["newusergroupid"];

					C::t("common_member")->insert($uid, $result["ucresult"]["username"], md5(random(10)), $result["ucresult"]["email"], $_G["clientip"], $groupid, $init_arr);
					$result["member"] = getuserbyuid($uid);
					$result["status"] = 1;
				}
			}

			if($result["status"] > 0) {

				if($this->extrafile && file_exists($this->extrafile)) {
					require_once $this->extrafile;
				}

				setloginstatus($result["member"], $_GET["cookietime"] ? 2592000 : 0);
				checkfollowfeed();

				C::t("common_member_status")->update($_G["uid"], array("lastip" => $_G["clientip"], "lastvisit" =>TIMESTAMP, "lastactivity" => TIMESTAMP));
				$ucsynlogin = $this->setting["allowsynlogin"] ? uc_user_synlogin($_G["uid"]) : "";

				if($invite["id"]) {
					$result = C::t("common_invite")->count_by_uid_fuid($invite["uid"], $uid);
					if(!$result) {
						C::t("common_invite")->update($invite["id"], array("fuid"=>$uid, "fusername"=>$_G["username"]));
						updatestat("invite");
					} else {
						$invite = array();
					}
				}
				if($invite["uid"]) {
					require_once libfile("function/friend");
					friend_make($invite["uid"], $invite["username"], false);
					dsetcookie("invite_auth", "");
					if($invite["appid"]) {
						updatestat("appinvite");
					}
				}

				$param = array(
					"username" => $result["ucresult"]["username"],
					"usergroup" => $_G["group"]["grouptitle"],
					"uid" => $_G["member"]["uid"],
					"groupid" => $_G["groupid"],
					"syn" => $ucsynlogin ? 1 : 0
				);

				$extra = array(
					"showdialog" => true,
					"locationtime" => true,
					"extrajs" => $ucsynlogin
				);

				$loginmessage = $_G["groupid"] == 8 ? "login_succeed_inactive_member" : "login_succeed";

				$location = $invite || $_G["groupid"] == 8 ? "home.php?mod=space&do=home" : dreferer();
				if(empty($_GET["handlekey"]) || !empty($_GET["lssubmit"])) {
					if(defined("IN_MOBILE")) {
						showmessage("location_login_succeed_mobile", $location, array("username" => $result["ucresult"]["username"]), array("location" => true));
					} else {
						if(!empty($_GET["lssubmit"])) {
							if(!$ucsynlogin) {
								$extra["location"] = true;
							}
							showmessage($loginmessage, $location, $param, $extra);
						} else {
							$href = str_replace(""", """, $location);
							showmessage("location_login_succeed", $location, array(),
								array(
									"showid" => "succeedmessage",
									"extrajs" => "<script type="text/javascript">".
										"setTimeout("window.location.href ="".$href."";", 3000);".
										"$("succeedmessage_href").href = "".$href."";".
										"$("main_message").style.display = "none";".
										"$("main_succeed").style.display = "";".
										"$("succeedlocation").innerHTML = "".lang("message", $loginmessage, $param)."";</script>".$ucsynlogin,
									"striptags" => false,
									"showdialog" => true
								)
							);
						}
					}
				} else {
					showmessage($loginmessage, $location, $param, $extra);
				}
			} else {
				$password = preg_replace("/^(.{".round(strlen($_GET["password"]) / 4)."})(.+?)(.{".round(strlen($_GET["password"]) / 6)."})$/s", "\1***\3", $_GET["password"]);
				$errorlog = dhtmlspecialchars(
					TIMESTAMP."	".
					($result["ucresult"]["username"] ? $result["ucresult"]["username"] : $_GET["username"])."	".
					$password."	".
					"Ques #".intval($_GET["questionid"])."	".
					$_G["clientip"]);
				writelog("illegallog", $errorlog);
				loginfailed($_GET["username"]);
				$fmsg = $result["ucresult"]["uid"] == "-3" ? (empty($_GET["questionid"]) || $answer == "" ? "login_question_empty" : "login_question_invalid") : "login_invalid";
				if($_G["member_loginperm"] > 1) {
					showmessage($fmsg, "", array("loginperm" => $_G["member_loginperm"] - 1));
				} elseif($_G["member_loginperm"] == -1) {
					showmessage("login_password_invalid");
				} else {
					showmessage("login_strike");
				}
			}

		}

	}

很明显,表单未提交执行的是:

if(!submitcheck("loginsubmit", 1, $seccodestatus)) {

}

这里我们是登录操作,所以只需要看else部分,else部分其中有如下代码:

if($result["status"] > 0) {

	if($this->extrafile && file_exists($this->extrafile)) {
		require_once $this->extrafile;
	}

	setloginstatus($result["member"], $_GET["cookietime"] ? 2592000 : 0);
	checkfollowfeed();
..........
}

$result["status"]>0,肯定是登录成功的,现在来看函数 setloginstatus($result["member"], $_GET["cookietime"] ? 2592000 : 0);

下面找到此函数文件 source/function/function_member.php

function setloginstatus($member, $cookietime) {
	global $_G;
	$_G["uid"] = intval($member["uid"]);
	$_G["username"] = $member["username"];
	$_G["adminid"] = $member["adminid"];
	$_G["groupid"] = $member["groupid"];
	$_G["formhash"] = formhash();
	$_G["session"]["invisible"] = getuserprofile("invisible");
	$_G["member"] = $member;
	loadcache("usergroup_".$_G["groupid"]);
	C::app()->session->isnew = true;
	C::app()->session->updatesession();

	dsetcookie("auth", authcode("{$member["password"]}	{$member["uid"]}", "ENCODE"), $cookietime, 1, true);
	dsetcookie("loginuser");
	dsetcookie("activationauth");
	dsetcookie("pmnum");

	include_once libfile("function/stat");
	updatestat("login", 1);
	if(defined("IN_MOBILE")) {
		updatestat("mobilelogin", 1);
	}
	if($_G["setting"]["connect"]["allow"] && $_G["member"]["conisbind"]) {
		updatestat("connectlogin", 1);
	}
	$rule = updatecreditbyaction("daylogin", $_G["uid"]);
	if(!$rule["updatecredit"]) {
		checkusergroup($_G["uid"]);
	}
}

其中代码  dsetcookie("auth", authcode("{$member["password"]} {$member["uid"]}", "ENCODE"), $cookietime, 1, true);  把auth保存到cookie中,cookie有效时间是2592000秒,即30天

登录成功后打开Chrome浏览器本地cookie


今天是2014年4月23日,可看到cookie中有3个值的到期时间是2014年5月23日,已经圈红,可能与我们的自动登录有关,其中ZRcL_2132_auth应该就是我们刚才保存的auth


自动登录肯定是在系统初始化时操作的,经过搜索这几个cookie名称,我们定位到: x25/source/class/discuz/discuz_application.php   此文件是系统核心文件,在入口处执行,下面是方法  _init_input() 中的代码:
if(empty($this->var["cookie"]["saltkey"])) {
	$this->var["cookie"]["saltkey"] = random(8);
	dsetcookie("saltkey", $this->var["cookie"]["saltkey"], 86400 * 30, 1, 1);
}
$this->var["authkey"] = md5($this->var["config"]["security"]["authkey"].$this->var["cookie"]["saltkey"]);

其中 $this->var 就是全局变量 $_G ,在此文件的 _init_env() 方法中有定义:

$this->var = & $_G;

其实上面的操作就是判断当前是否已经设有cookie saltkey,没有的话随机生成一个,然后保存到cookie中 然后 MD5加密当前的$_config_security_authkey 与 saltkey,保存到$_G["authkey"]中 下面我们看$_G["authkey"]是干什么的,查看 x25/source/function/function_core.php 中的authcode()函数
function authcode($string, $operation = "DECODE", $key = "", $expiry = 0) {
	$ckey_length = 4;
	$key = md5($key != "" ? $key : getglobal("authkey"));
	$keya = md5(substr($key, 0, 16));
	$keyb = md5(substr($key, 16, 16));
	$keyc = $ckey_length ? ($operation == "DECODE" ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : "";

	$cryptkey = $keya.md5($keya.$keyc);
	$key_length = strlen($cryptkey);

	$string = $operation == "DECODE" ? base64_decode(substr($string, $ckey_length)) : sprintf("%010d", $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
	$string_length = strlen($string);

	$result = "";
	$box = range(0, 255);

	$rndkey = array();
	for($i = 0; $i <= 255; $i++) {
		$rndkey[$i] = ord($cryptkey[$i % $key_length]);
	}

	for($j = $i = 0; $i < 256; $i++) {
		$j = ($j + $box[$i] + $rndkey[$i]) % 256;
		$tmp = $box[$i];
		$box[$i] = $box[$j];
		$box[$j] = $tmp;
	}

	for($a = $j = $i = 0; $i < $string_length; $i++) {
		$a = ($a + 1) % 256;
		$j = ($j + $box[$a]) % 256;
		$tmp = $box[$a];
		$box[$a] = $box[$j];
		$box[$j] = $tmp;
		$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
	}

	if($operation == "DECODE") {
		if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
			return substr($result, 26);
		} else {
			return "";
		}
	} else {
		return $keyc.str_replace("=", "", base64_encode($result));
	}

}

注意其中的  $key = md5($key != "" ? $key : getglobal("authkey"));  原来authkey是默认的加密密钥, 看来之前生成的 auth 是和系统配置文件$_config_security_authkey 及随机值 saltkey 相关的,那么如果没有saltkey,是无法解密auth的,也就无法实现自动登录了。
下面查看 discuz_application.php 中的 _init_user() 方法,其中有如下代码:
if($auth = getglobal("auth", "cookie")) {
	$auth = daddslashes(explode("	", authcode($auth, "DECODE")));
}
list($discuz_pw, $discuz_uid) = empty($auth) || count($auth) < 2 ? array("", "") : $auth;

if($discuz_uid) {
	$user = getuserbyuid($discuz_uid, 1);
}

首先获取当前COOKIE中的auth,    $auth = getglotal("auth", "cookie")  ,如果有,则执行下面操作:
$auth = daddslashes(explode(" ", authcode($auth, "DECODE")));

解密出auth,然后就直接获取 password 和 uid 了
list($discuz_pw, $discuz_uid) = empty($auth) || count($auth) < 2 ? array("", "") : $auth;

至此结束!!!!!!!!

声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。