这里以DISCUZ X2.5 为例, X3和X2.5几乎一样
自动登录操作时,系统做了哪些操作
选中自动登录,查看网页代码如下:
<form method="post" autocomplete="off" id="lsform" action="member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yes" οnsubmit="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:;" οnclick="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("\t", 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("\t", 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']."\t".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."\t".
($result['ucresult']['username'] ? $result['ucresult']['username'] : $_GET['username'])."\t".
$password."\t".
"Ques #".intval($_GET['questionid'])."\t".
$_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']}\t{$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']}\t{$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("\t", 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("\t", authcode($auth, 'DECODE')));
解密出auth,然后就直接获取 password 和 uid 了
list($discuz_pw, $discuz_uid) = empty($auth) || count($auth) < 2 ? array('', '') : $auth;
至此结束!!!!!!!!