JSP网站与phpwind论坛同步登入的解决思路
在上一篇文章中,笔者介绍了在Windows上安装Apache、PHP、MySQL等软件的过程,以及Apache与Resin整合的方法。在这篇文章中,笔者将介绍JSP网站如何与phpwind论坛实现同步登入和同步退出的方案思路。本文以phpwind 7.5 SP3的源码为例,但由于笔者曾在源码中插入过一些测试代码,后虽作了删除,但尚不保证下文中所示代码的行数完全与源码一致,请自行在提示行数所定位的上下几行中查找。
一、phpwind的Cookie是如何产生的
phpwind是通过Cookie的方式来判断当前用户是否已经登入,因此要实现JSP网站与phpwind同步登入和同步退出功能的要点,就在于JSP网站的程序能捕获并分析phpwind生成的Cookie,同时JSP网站的程序还应能生成同样的Cookie,以便访问phpwind论坛时可以自动登入。下面来看下phpwind对于登入用户所生成的Cookie的信息:
在上图的Cookie信息中,08383_winduser及其所对应的值就是判断phpwind登入与退出状态的依据,对它的分析也是本文的重点所在。
先来看下08383_winduser中08383是如何生成的。在login.php文件的84行找到:
Cookie("winduser",StrCode($winduid."\t".$windpwd."\t".$safecv),$cktime);
Cookie函数的作用就是向客户端写Cookie,其函数定义在common.php文件的145行:
function Cookie($ck_Var,$ck_Value,$ck_Time='F',$p=true){ }
从中可知第一个参数是Cookie的键名,第二个参数是Cookie的值,这样通过函数调用传递给$ck_Var的值就是winduser,但我们在Cookie信息中看到的却是08383_winduser,那么这08383是从何而来呢?细看下面的代码,我们会发现在173行有这样的语句:
$ck_Var = CookiePre().'_'.$ck_Var;
看来08383必定是由CookiePre函数所产生的,找到189行CookiePre函数的定义:
function CookiePre() {
static $pre = null;
!isset($pre) && $pre = substr(md5($GLOBALS['db_sitehash']),0,5);
return $pre;
}
这就知道了原来此08383是md5($GLOBALS['db_sitehash'])的前5个字符,而$GLOBALS['db_sitehash']是pw_config表中的数据db_sitehash所对应的值,这样键名的产生过程就清楚了。下面再来看如何生成键值。
从上文中可知键值其实就是StrCode($winduid."\t".$windpwd."\t".$safecv),看common.php文件中第241行StrCode函数的定义:
function StrCode($string,$action='ENCODE') { }
可以知道第一个参数是被加密或解密的字符串,第二个参数是表示加密(ENCODE)或是解密(DECODE),默认为加密。其代码中$GLOBALS['pwServer']['HTTP_USER_AGENT']表示的是Request请求中的User-Agent头信息,$GLOBALS['db_hash'] 是pw_config表中的数据db_hash所对应的值。那么传递给StrCode函数的实参$winduid."\t".$windpwd."\t".$safecv又是如何产生的呢?
在login.php文件的第66与70行分别有如下代码:
$logininfo = checkpass($pwuser, $md5_pwpwd, $safecv, $lgt);
list($winduid, $groupid, $windpwd, $showmsginfo) = $logininfo;
由此可知StrCode函数的实参是由checkpass函数产生的,在checkpass.php文件第49行找到checkpass函数的定义:
function checkpass($username, $password, $safecv, $lgt=0) { }
可以知道第一个参数是用户名,第二个参数是md5加密过的用户密码,第三个参数是经过处理的用户安全问题。其返回值中的第一个值、第三个值分别与StrCode函数实参中的$winduid、$windpwd相对应,其中$winduid为pw_members表中对应的uid。而$windpwd的产生较为复杂,在第111行可以看到如下代码:
$windpwd = PwdCode($password);
因此可知$windpwd是由PwdCode函数所产生的,在common.php文件的第227行找到PwdCode函数定义:
function PwdCode($pwd) {
return md5($GLOBALS['pwServer']['HTTP_USER_AGENT'].$pwd.$GLOBALS['db_hash']);
}
这样就明白了,$windpwd其实就是Request请求中的User-Agent头信息的字符串、用md5加密过的用户密码、pw_config表中数据db_hash所对应值,三者依次拼接而成的字符串进行一次md5加密之后所产生的字符串,真是够复杂的,看来phpwind为了安全也是煞费苦心啊!
最后StrCode函数实参中就剩下$safecv了,它是由questcode函数产生,在checkpass.php文件的第211行找到questcode函数的定义:
function questcode($question,$customquest,$answer) {
$question = $question=='-1' ? $customquest : $question;
return $question ? substr(md5(md5($question).md5($answer)),8,10) : '';
}
$safecv的产生过程也够复杂的,相信大家通过上面的讲解也能明白这个产生过程了,那么笔者也就不多费口舌,太累人了!
二、phpwind是如何从Cookie中获取信息
Phpwind从Cookie中获取用户信息的代码在global.php文件第152行:
list($winduid,$windpwd,$safecv)=explode("\t",addslashes(StrCode(GetCookie('winduser'),'DECODE')));
其重点在于StrCode(GetCookie('winduser'),'DECODE'),作用是将从Cookie中获取的值采用base64方式进行解码。解码之后的字符串用"\t"分割为三段,第一段是uid,第二段是由上文所提到的PwdCode函数生成的字符串,第三段是由上文所提到的questcode函数生成的字段串,分别存入$winduid、$windpwd与$safecv中。之后就可以通过该uid来获取用户信息,并验证相关的密码与安全问题是否一致了。
三、实现与phpwind论坛同步登入和同步退出的思路
要做到同步登入和同步退出,即用户在JSP网站这边登入后访问phpwind时用户已为登入状态,在phpwind登入后访问JSP网站也为登入状态,退出亦然。这样就要求在JSP网站这边用java实现两个功能,一个是从Cookie中读取由phpwind生成的winduser的值并加以解析,另一个是用户登入时采用类似于phpwind的方式生成Cookie,以便phpwind论坛识别用户状态。由于实现的代码较为简单,只要将上文提到的这些函数翻译成java的代码就行了,因此笔者在此主要介绍一下思路和可能遇到的问题。
对于从Cookie中读取由phpwind生成的winduser的值并加以解析,笔者是将这部分代码写在一个Filter里。在此Filter中,读取Cookie中的值然后调用StrCode函数进行解码,从而得到uid。之后通过uid查询数据库得到用户对象,验证其密码与安全问题是否正确。如果都正确的话,则将用户对象存入Session中。下次需要用到该用户对象时,直接从Session中获取,而不再通过调用StrCode函数进行解码及查询数据库得到用户对象。
这其中要注意解决的问题是,由于采用在Session中传递用户对象,可能会出现phpwind论坛已经退出或改换用户重新登入,但是在另一浏览窗口访问的JSP网站,由于Session中的对象没有失效,继续访问别的页面时还处于原用户登入状态。笔者的解决方法是设置一个静态变量来保存Cookie中winduser的值,一旦发现新值变空或与原保留值不同,则说明在phpwind论坛那边已经退出或以新用户名登入了。
对于JSP网站这边,用户登入时调用StrCode函数来生成类似phpwind方式的Cookie,这样以便phpwind论坛识别用户状态。此外,为了使phpwind与JSP网站共用一个Cookie,需要修改phpwind中的config.php文件,将$db_ckdomain设置“.XXX.com”的形式。同步退出的实现较为简单,只要将Cookie中的值置空即可,笔者在此不再赘述。
好了,在本文中笔者主要介绍了phpwind的Cookie产生方式,以及phpwind是如何从Cookie中获取信息,并简要介绍了与phpwind论坛实现同步登入的思路。在下一篇文章中,笔者将介绍一下如何将Oracle中的数据快速便利的导入到MySQL之中。