ThinkPHP下表单令牌错误与解决方法分析
本文实例讲述了ThinkPHP下表单令牌错误与解决方法。分享给大家供大家参考,具体如下:
在项目的开发过程中,添加、编辑数据时偶尔会遇到系统提示的“表单令牌错误”,一开始没怎么在意,直到今天下午QA把此问题提到bug系统了,正好时间也有空余,就追着TP3.13的源码看了下去,几分钟后,便知道原委了。
在项目中开启表单令牌,通常要在配置文件中做如下配置
//是否开启令牌验证 'TOKEN_ON'=>true, //令牌验证的表单隐藏字段名称 'TOKEN_NAME'=>'__hash__', //令牌哈希验证规则默认为MD5 'TOKEN_TYPE'=>'md5', //令牌验证出错后是否重置令牌默认为true 'TOKEN_RESET'=>true
以编辑数据为例,通常在服务端有个Model写上字段过滤规则,Action写上数据检测的代码,如
$table=D('table'); if(!$table->create()){ exit($this->error($table->getError())); }
这时在IDE上双击create()定位到TP框架中Model.class.php中的create方法
/** *创建数据对象但不保存到数据库 *@accesspublic *@parammixed$data创建数据 *@paramstring$type状态 *@returnmixed */ publicfunctioncreate($data='',$type=''){ ……省略…… //表单令牌验证 if(!$this->autoCheckToken($data)){ $this->error=L('_TOKEN_ERROR_'); returnfalse; } ……省略…… }
看到代码会理解当autoCheckToken方法检测失败时会报错,那么就接着跟踪此方法
//自动表单令牌验证 //TODOajax无刷新多次提交暂不能满足 publicfunctionautoCheckToken($data){ //支持使用token(false)关闭令牌验证 //如果在Action写了D方法,但没有对应的Model文件,那么$this->options为空 if(isset($this->options['token'])&&!$this->options['token'])returntrue; if(C('TOKEN_ON')){ $name=C('TOKEN_NAME'); if(!isset($data[$name])||!isset($_SESSION[$name])){//令牌数据无效 returnfalse; } //令牌验证 list($key,$value)=explode('_',$data[$name]); if($value&&$_SESSION[$name][$key]===$value){//防止重复提交 unset($_SESSION[$name][$key]);//验证完成销毁session returntrue; } //开启TOKEN重置 if(C('TOKEN_RESET'))unset($_SESSION[$name][$key]); returnfalse; } returntrue; }
看了这段代码,会发现第一个判断中有$_SESSION[$name],那么这个seesion变量时从哪里过来的呢,这还得从生成令牌时说起,定位TokenBuildBehavior.class.php文件
//创建表单令牌 privatefunctionbuildToken(){ $tokenName=C('TOKEN_NAME'); $tokenType=C('TOKEN_TYPE'); if(!isset($_SESSION[$tokenName])){ $_SESSION[$tokenName]=array(); } //标识当前页面唯一性 $tokenKey=md5($_SERVER['REQUEST_URI']); if(isset($_SESSION[$tokenName][$tokenKey])){//相同页面不重复生成session $tokenValue=$_SESSION[$tokenName][$tokenKey]; }else{ $tokenValue=$tokenType(microtime(TRUE)); $_SESSION[$tokenName][$tokenKey]=$tokenValue; } $token=''; return$token; }
此段代码主要是在TP开启表单验证的情况下,以TOKEN_NAME和当前URI的md5为健生成令牌值,再在用户提交表单时,先验证下是否存在该session,没有则返回false,有则紧接着和表单字段TOKEN_NAME验证下,如果一致先删除此session(作用时避免下次提交出先表单令牌错误),返回ture,否则返回false。
ok,回到主题,TP下表单提交之所以会出现令牌错误,那么就只有两种可能
1.在令牌开启的状态下,提交的表单中,没有TOKEN_NAME字段或是没有相应session(当前提交表单环境下,没有生成相应session,这个主要是在用户提交后报错用户紧接着又刷新当前页面,同时编辑页面和展示页面是在同一个方法里)
2.有session变量,但前后值不一样
我们项目之所以出现此错误,可以看看下面配置
returnarray( 'TOKEN_ON'=>'false', 'TOKEN_NAME'=>'__hash__', 'TOKEN_TYPE'=>'md5', 'TOKEN_RESET'=>'true', 'DB_FIELDTYPE_CHECK'=>'true' );
本来应该写成布尔值的false,不知道哪位大侠任性的写成字符串的false了,那么判断时当然会按开启表单令牌的逻辑来,而且项目中,添加、编辑和展示都是同一个方法,一旦验证出错,一般程序处理逻辑会返回原有的界面,那么就和上次是同一个表单了,连续提交同一个表单也就相当于重复提交,那么便会报“表单令牌错误”。
希望本文所述对大家基于ThinkPHP框架的PHP程序设计有所帮助。