本文将详细介绍Discuz!X2验证码生成和验证验证码,以及通过PHP验证验证码的例子。此外,还介绍了随机数是如何产生的。有兴趣的可以参考一下,希望能帮到你。
一、验证码的产生1、如何在模板中添加一个验证码
在X2中验证码的模板部分独立为一个模板文件(template/default/common/seccheck.htm),供各个地方调用。
在模板中可以添加如下代码来调用验证码模板部分:
复制代码代码如下:
<!--{eval$seccodecheck=1;}-->
<!--{eval$sectpl='<tr><th><sec></th><td><sec><pclass="d"><sec></p></td>';}-->
<!--{subtemplatecommon/seccheck}-->
解释下这三句话:
第一句的意思为,我要开启验证码,即$seccodecheck
变量
必须为真,就表示当前页面要开启验证码。
第二句的意思为,给要显示出来的验证码设置一个显示的模板格式,$sectpl这个变量对应的就是模板,设置$sectpl可以让验证码的显示与当前页面的格式更好的结合。从示例的模板代码中可以看出,只有<sec>不属于HMTL标准代码,而且出现了3次,这3次分别代表:“验证码”文字、验证码输入框、验证码图片,如下图所示:
这样就可以把验证码不同的部分合理的安放在您的页面中了。
第三句的意思为,将独立的验证码模板合并到当前页面中,与当前页面的模板一同输出。
在模板中添加上如上的代码后,刷新页面就可以看到验证码部分了。
2、验证码的生成流程
(以X2默认设置的“英文图片验证码”为例)
1)刚出现的验证码会默认执行一段JS代码
复制代码代码如下:
<scripttype="text/javascript"reload="1">updateseccode('SQq29j20');</script>
执行的JS主要就是执行了updateseccode这个函数,直接点击验证码图片执行的也是这个函数。函数中的'SQq29j20'是当前页面验证码的唯一字符串idhash,他是由是否为Ajax请求、sessionid、自增数字组成,此处不必深究其含义。
2)updateseccode函数在static/js/common.js中
复制代码代码如下:
functionupdateseccode(idhash,play){
$F('_updateseccode',arguments);
}
通过上面代码可以看到,updateseccode又调用了_updateseccode私有函数,_updateseccode函数在static/js/common_extra.js文件中
复制代码代码如下:
function_updateseccode(idhash,play){
if(isUndefined(play)){
if($('seccode_'+idhash)){
$('seccodeverify_'+idhash).value='';
if(secST['code_'+idhash]){
clearTimeout(secST['code_'+idhash]);
}
$('checkseccodeverify_'+idhash).innerHTML='<imgsrc="'+IMGDIR+'/none.gif"width="16"height="16"class="vm"/>';
ajaxget('misc.php?mod=seccode&action=update&idhash='+idhash,'seccode_'+idhash,null,'','',function(){
secST['code_'+idhash]=setTimeout(function(){$('seccode_'+idhash).innerHTML='<spanclass="xi2cur1"onclick="updateseccode(''+idhash+'')">刷新验证码</span>';},180000);
});
}
}else{
eval('window.document.seccodeplayer_'+idhash+'.SetVariable("isPlay","1")');
}
}
这段JS代码有两个含义:
一是通过ajaxget请求了misc.php?mod=seccode&action=update&idhash=xxxx这样一个地址
二是设定了一个
定时器
,从显示了验证码开始,3分钟后自动将验证码图片换为“刷新验证码”的文字,点击该文字就执行updateseccode这个函数,重新更新验证码。由此可以看出,此种方式可以很好的解决验证码过期的问题。
3)找到通过ajaxget请求的程序source/module/misc/misc_seccode.php
通过url中的action=update可以看出,应该查看if($_G['gp_action']=='update'){……}中的一段
复制代码代码如下:
if($_G['gp_action']=='update'){
$message='';
if($_G['setting']['seccodestatus']){
$rand=random(5,1);
$flashcode='';
$idhash=isset($_G['gp_idhash'])?$_G['gp_idhash']:'';
$ani=$_G['setting']['seccodedata']['animator']?'_ani':'';
if($_G['setting']['seccodedata']['type']==2){
……
}elseif($_G['setting']['seccodedata']['type']==3){
…...
}else{
$message=lang('core','seccode_image'.$ani.'_tips').'<imgonclick="updateseccode(''.$idhash.'')"width="'.$_G['setting']['seccodedata']['width'].'"height="'.$_G['setting']['seccodedata']['height'].'"src="misc.php?mod=seccode&update='.$rand.'&idhash='.$idhash.'"class="vm"alt=""/>';
}
}
includetemplate('common/header_ajax');
echolang('message',$message,array('flashcode'=>$flashcode,'idhash'=>$idhash));
includetemplate('common/footer_ajax');
}
默认设置的“英文图片验证码”的$_G['setting']['seccodedata']['type']为0,所以看else的部分。仔细看这里就是按照ajax的格式返回了一个验证码的图片,但是图片的src为misc.php?mod=seccode&update=$rand&idhash=$idhash这样一个动态链接,所以是通过这个链接动态生成的图片,此时又产生了一个新的请求。
4)找到通过图片链接请求的程序source/module/misc/misc_seccode.php(和上面是同一个文件)
通过url可以看出,应该查看if($_G['gp_action']=='update'){……}else{……}中的一段
复制代码代码如下:
}else{
$refererhost=parse_url($_SERVER['HTTP_REFERER']);
$refererhost['host'].=!empty($refererhost['port'])?(':'.$refererhost['port']):'';
if($_G['setting']['seccodedata']['type']<2&&($refererhost['host']!=$_SERVER['HTTP_HOST']||!$_G['setting']['seccodestatus'])||$_G['setting']['seccodedata']['type']==2&&!extension_loaded('ming')&&$_POST['fromFlash']!=1||$_G['setting']['seccodedata']['type']==3&&$_GET['fromFlash']!=1){
exit('AccessDenied');
}
$seccode=make_seccode($_G['gp_idhash']);
if(!$_G['setting']['nocacheheaders']){
@header("Expires:-1");
@header("Cache-Control:no-store,private,post-check=0,pre-check=0,max-age=0",FALSE);
@header("Pragma:no-cache");
}
require_oncelibfile('class/seccode');
$code=newseccode();
$code->code=$seccode;
$code->type=$_G['setting']['seccodedata']['type'];
$code->width=$_G['setting']['seccodedata']['width'];
$code->height=$_G['setting']['seccodedata']['height'];
$code->background=$_G['setting']['seccodedata']['background'];
$code->adulterate=$_G['setting']['seccodedata']['adulterate'];
$code->ttf=$_G['setting']['seccodedata']['ttf'];
$code->angle=$_G['setting']['seccodedata']['angle'];
$code->warping=$_G['setting']['seccodedata']['warping'];
$code->scatter=$_G['setting']['seccodedata']['scatter'];
$code->color=$_G['setting']['seccodedata']['color'];
$code->size=$_G['setting']['seccodedata']['size'];
$code->shadow=$_G['setting']['seccodedata']['shadow'];
$code->animator=$_G['setting']['seccodedata']['animator'];
$code->fontpath=DISCUZ_ROOT.'./static/image/seccode/font/';
$code->datapath=DISCUZ_ROOT.'./static/image/seccode/';
$code->includepath=DISCUZ_ROOT.'./source/class/';
$code->display();
}
这部分开始是先做了一些安全性的验证,最后是根据给定的参数和由make_seccode生成的验证码字符串,生成验证码的图片,所以中间是重点。
make_seccode($_G['gp_idhash'])这个函数传入了当前页面验证码的唯一字符串idhash,生成了用于验证码的字符串。
5)make_seccode函数在source/function/function_seccode.php文件
复制代码代码如下:
functionmake_seccode($idhash){
global$_G;
$seccode=random(6,1);
$seccodeunits='';
if($_G['setting']['seccodedata']['type']==1){
$lang=lang('seccode');
$len=strtoupper(CHARSET)=='GBK'?2:3;
$code=array(substr($seccode,0,3),substr($seccode,3,3));
$seccode='';
for($i=0;$i<2;$i++){
$seccode.=substr($lang['chn'],$code[$i]*$len,$len);
}
}elseif($_G['setting']['seccodedata']['type']==3){
$s=sprintf('%04s',base_convert($seccode,10,20));
$seccodeunits='CEFHKLMNOPQRSTUVWXYZ';
}else{
$s=sprintf('%04s',base_convert($seccode,10,24));
$seccodeunits='BCEFGHJKMPQRTVWXY2346789';
}
if($seccodeunits){
$seccode='';
for($i=0;$i<4;$i++){
$unit=ord($s{$i});
$seccode.=($unit>=0x30&&$unit<=0x39)?$seccodeunits[$unit-0x30]:$seccodeunits[$unit-0x57];
}
}
dsetcookie('seccode'.$idhash,authcode(strtoupper($seccode)."t".(TIMESTAMP-180)."t".$idhash."t".FORMHASH,'ENCODE',$_G['config']['security']['authkey']),0,1,true);
return$seccode;
}
从函数中可以看到,验证码$seccode首先来自一个6位的随机数字random(6,1)(此函数如何工作,最后讲解)。
默认设置的“英文图片验证码”的$_G['setting']['seccodedata']['type']为0,所以看else的部分。将$seccode的数字通过base_convert函数由10
进制
转为24进制,然后设定可以在验证码出现的字符串
复制代码代码如下:
'BCEFGHJKMPQRTVWXY2346789'。
最后将24进制的验证码在$seccodeunits中取得真正的4位验证码字符串$seccode,最后将$seccode通过authcode加密函数进行加密,写入cookie中,并返回,cookie的名字是seccode连上$idhash的值(例如:seccodeSQq29j20)。加密时使用的是在config/config_global.php中设置的$_G['config']['security']['authkey']的值。
至此验证码及图片生成完毕,生成的验证码到目前为止只以加密的方式存在于cookie中。
二、验证码的验证
1、JS方式的验证
1)这种验证就是在文本框中输入验证码后,及时的验证。
这个验证是由文本框的onblur失去焦点事件触发checksec('code','SQq29j20')JS函数进行验证的。
2)checksec函数在static/js/common.js中
复制代码代码如下:
functionchecksec(type,idhash,showmsg,recall){
$F('_checksec',arguments);
}
通过上面代码可以看到,checksec又调用了_checksec私有函数,_checksec函数在static/js/common_extra.js文件中
复制代码代码如下:
function_checksec(type,idhash,showmsg,recall){
varshowmsg=!showmsg?0:showmsg;
varsecverify=$('sec'+type+'verify_'+idhash).value;
if(!secverify){
return;
}
varx=newAjax('XML','checksec'+type+'verify_'+idhash);
x.loading='';
$('checksec'+type+'verify_'+idhash).innerHTML='<imgsrc="'+IMGDIR+'/loading.gif"width="16"height="16"class="vm"/>';
x.get('misc.php?mod=sec'+type+'&action=check&inajax=1&&idhash='+idhash+'&secverify='+(BROWSER.ie&&document.charset=='utf-8'?encodeURIComponent(secverify):secverify),function(s){
varobj=$('checksec'+type+'verify_'+idhash);
obj.style.display='';
if(s.substr(0,7)=='succeed'){
obj.innerHTML='<imgsrc="'+IMGDIR+'/check_right.gif"width="16"height="16"class="vm"/>';
if(showmsg){
recall(1);
}
}else{
obj.innerHTML='<imgsrc="'+IMGDIR+'/check_error.gif"width="16"height="16"class="vm"/>';
if(showmsg){
if(type=='code'){
showError('验证码错误,请重新填写');
}elseif(type=='qaa'){
showError('验证问答错误,请重新填写');
}
recall(0);
}
}
});
}
这个函数首先验证下,输入框内填写的验证码的值$('sec'+type+'verify_'+idhash).value是否存在(type就是传入的code)。然后通过ajax请求访问misc.php?mod=seccode&action=check&inajax=1&&idhash=xxxx&secverify=xxxx这样一个地址,这个地址会返回验证的结果字符串。如果返回结果的前7个字符是succeed则验证通过,显示对勾;否则提示“验证码错误,请重新填写”,并显示红叉。
3)找到通过ajax请求的程序source/module/misc/misc_seccode.php
通过url中的action=check可以看出,应该查看elseif($_G['gp_action']=='check'){……}中的一段
复制代码代码如下:
}elseif($_G['gp_action']=='check'){
includetemplate('common/header_ajax');
echocheck_seccode($_G['gp_secverify'],$_G['gp_idhash'])?'succeed':'invalid';
includetemplate('common/footer_ajax');
}else{
这里将通过url传入的secverify和idhash两个值传递给check_seccode函数,通过代码看到check_seccode返回布尔值,故结果为真,则通过验证,返回succeed字符串,结果为假,则验证失败,返回invalid字符串。
4)check_seccode函数在source/function/function_core.php文件
复制代码代码如下:
functioncheck_seccode($value,$idhash){
global$_G;
if(!$_G['setting']['seccodestatus']){
returntrue;
}
if(!isset($_G['cookie']['seccode'.$idhash])){
returnfalse;
}
list($checkvalue,$checktime,$checkidhash,$checkformhash)=explode("t",authcode($_G['cookie']['seccode'.$idhash],'DECODE',$_G['config']['security']['authkey']));
return$checkvalue==strtoupper($value)&&TIMESTAMP-180>$checktime&&$checkidhash==$idhash&&FORMHASH==$checkformhash;
}
此函数首先根据缓存中的设定验证验证码的开启状态,如果未开启,此处验证直接返回真,既然没有开启验证码自然如何验证均为真。
然后验证cookie中是否存在生成验证码时写入cookie的值(例如:seccodeSQq29j20),如果cookie没有此值,则此次验证失效,需要重新生成验证码,重新验证。
最后从cookie取出值,使用$_G['config']['security']['authkey']加密串,通过authcode函数对值进行解密,解密后获取到验证码、生成时间、idhash、formhash四个值。然后需要同时满足以下四个条件才可以通过验证:
-输入的验证码等于解密出来的验证码
-验证码的生成时间距当前时间小于180秒
-传入的idhash等于解密出来的idhash
-当前系统生成的formhash等于解密出来的formhash
至此通过JS方式的验证码验证完成。
2、PHP方式的验证
1)这种方式就是在验证码所在的表单提交后,对输入的验证码进行的验证。
例如在修改用户密码时开启了验证码,则会在其处理的PHP程序中发现(source/include/spacecp/spacecp_profile.php)这样一句代码
submitcheck('passwordsubmit',0,$seccodecheck,$secqaacheck)
submitcheck函数就是对提交的表单进行验证的。
2)submitcheck函数在source/function/function_core.php文件
复制代码代码如下:
functionsubmitcheck($var,$allowget=0,$seccodecheck=0,$secqaacheck=0){
if(!getgpc($var)){
returnFALSE;
}else{
global$_G;
if($allowget||($_SERVER['REQUEST_METHOD']=='POST'&&!empty($_G['gp_formhash'])&&$_G['gp_formhash']==formhash()&&empty($_SERVER['HTTP_X_FLASH_VERSION'])&&(empty($_SERVER['HTTP_REFERER'])||
preg_replace("/https?://([^:/]+).*/i","1",$_SERVER['HTTP_REFERER'])==preg_replace("/([^:]+).*/","1",$_SERVER['HTTP_HOST'])))){
if(checkperm('seccode')){
if($secqaacheck&&!check_secqaa($_G['gp_secanswer'],$_G['gp_sechash'])){
showmessage('submit_secqaa_invalid');
}
if($seccodecheck&&!check_seccode($_G['gp_seccodeverify'],$_G['gp_sechash'])){
showmessage('submit_seccode_invalid');
}
}
returnTRUE;
}else{
showmessage('submit_invalid');
}
}
}
submitcheck函数一般只填写前两个参数即可,第一个参数表示要验证的表单元素的名字,此表单元素不存在则验证失败;第二个参数表示是否允许通过GET方式提交的数据通过验证,0为不允许,1为允许,一般为0即可。
后两个参数用于表示提交的表单中是否需要对验证码和验证问答做验证,第三个参数$seccodecheck代表验证码,第四个参数$secqaacheck代表验证问答,参数值都是0为不验证,1为验证。
所以如果需要在提交后验证验证码,则至少要填写3个参数,即submitcheck('passwordsubmit',0,1)。
进入函数中会现对提交表单的提交方式、formhash、访问来源referer等数据进行安全性验证,通过后则会调用check_seccode函数对提交过来的验证码进行验证了,根据check_seccode的返回值,来给予不同的提示。check_seccode函数如何工作的参看JS验证中的4)即可。
至此通过PHP方式的验证码验证完成。
三、随机数如何产生的
Discuz!X的随机数是通过random函数产生的,函数在source/function/function_core.php文件
复制代码代码如下:
functionrandom($length,$numeric=0){
$seed=base_convert(md5(microtime().$_SERVER['DOCUMENT_ROOT']),16,$numeric?10:35);
$seed=$numeric?(str_replace('0','',$seed).'012340567890'):($seed.'zZ'.strtoupper($seed));
$hash='';
$max=strlen($seed)-1;
for($i=0;$i<$length;$i++){
$hash.=$seed{mt_rand(0,$max)};
}
return$hash;
}
此函数有两个参数,$length表示要获取的随机数的位数,$numeric表示是否要获取纯数字的随机数,取值0或1。
函数首先使用microtime函数获取当前的微秒级时间戳字符串,然后在后面拼接上单前网站的根目录路径,然后进行MD5加密,获得32位长的字符串。之后对其进行转进制,如果要获取纯数字的随机数,则从16进制转为10进制,如果要获得数字和英文混杂的随机数,则从16进制转为35进制。之后再将转进制后获得的字符串,根据是否要获取纯数字随机数的区别,进行拼接。最后从拼接后的字符串中随机抽取随机数的第一位、第二位以此类推,直至获取满足要求的随机数的位置为止。至此生成了随机数。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)