你现在的位置:首页 > PHP网站建设知识库 > Discuz > 正文

discuz自动登录功能原理分析

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

自动登录操作时,系统做了哪些操作

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

<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("\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')) {  
<span style="white-space:pre">  </span>$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;