ThinkPHP下表单令牌错误与解决方法分析


Posted in PHP onMay 20, 2017

本文实例讲述了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方法

/**
* 创建数据对象 但不保存到数据库
* @access public
* @param mixed $data 创建数据
* @param string $type 状态
* @return mixed
*/
public function create($data='',$type='') {
  ……省略……
  // 表单令牌验证
  if(!$this->autoCheckToken($data)) {
    $this->error = L('_TOKEN_ERROR_');
    return false;
  }
  ……省略……
}

看到代码会理解当autoCheckToken方法检测失败时会报错,那么就接着跟踪此方法

// 自动表单令牌验证
// TODO ajax无刷新多次提交暂不能满足
public function autoCheckToken($data) {
  // 支持使用token(false) 关闭令牌验证
  // 如果在Action写了D方法,但没有对应的Model文件,那么$this->options为空
  if(isset($this->options['token']) && !$this->options['token']) return true;
  if(C('TOKEN_ON')){
    $name  = C('TOKEN_NAME');
    if(!isset($data[$name]) || !isset($_SESSION[$name])) { // 令牌数据无效
      return false;
    }
    // 令牌验证
    list($key,$value) = explode('_',$data[$name]);
    if($value && $_SESSION[$name][$key] === $value) { // 防止重复提交
      unset($_SESSION[$name][$key]); // 验证完成销毁session
      return true;
    }
    // 开启TOKEN重置
    if(C('TOKEN_RESET')) unset($_SESSION[$name][$key]);
    return false;
  }
  return true;
}

看了这段代码,会发现第一个判断中有$_SESSION[$name],那么这个seesion变量时从哪里过来的呢,这还得从生成令牌时说起,定位TokenBuildBehavior.class.php文件

// 创建表单令牌
private function buildToken() {
  $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   = '<input type="hidden" name="'.$tokenName.'" value="'.$tokenKey.'_'.$tokenValue.'" />';
  return $token;
}

此段代码主要是在TP开启表单验证的情况下,以TOKEN_NAME和当前URI的md5为健生成令牌值,再在用户提交表单时,先验证下是否存在该session,没有则返回false,有则紧接着和表单字段TOKEN_NAME验证下,如果一致先删除此session(作用时避免下次提交出先表单令牌错误),返回ture,否则返回false。

ok,回到主题,TP下表单提交之所以会出现令牌错误,那么就只有两种可能

1. 在令牌开启的状态下,提交的表单中,没有TOKEN_NAME字段或是没有相应session(当前提交表单环境下,没有生成相应session,这个主要是在用户提交后报错用户紧接着又刷新当前页面,同时编辑页面和展示页面是在同一个方法里)

2. 有session变量,但前后值不一样

我们项目之所以出现此错误,可以看看下面配置

return array (
  'TOKEN_ON' => 'false',
  'TOKEN_NAME' => '__hash__',
  'TOKEN_TYPE' => 'md5',
  'TOKEN_RESET' => 'true',
  'DB_FIELDTYPE_CHECK' => 'true'
);

本来应该写成布尔值的false,不知道哪位大侠任性的写成字符串的false了,那么判断时当然会按开启表单令牌的逻辑来,而且项目中,添加、编辑和展示都是同一个方法,一旦验证出错,一般程序处理逻辑会返回原有的界面,那么就和上次是同一个表单了,连续提交同一个表单也就相当于重复提交,那么便会报“表单令牌错误”。

希望本文所述对大家基于ThinkPHP框架的PHP程序设计有所帮助。

PHP 相关文章推荐
在PHP中使用灵巧的体系结构
Oct 09 PHP
PHP调用C#开发的dll类库方法
Jul 28 PHP
使用php方法curl抓取AJAX异步内容思路分析及代码分享
Aug 25 PHP
解决php表单重复提交实现方法
Sep 29 PHP
超详细的php用户注册页面填写信息完整实例(附源码)
Nov 17 PHP
前端必学之PHP语法基础
Jan 01 PHP
thinkphp3.x中session方法的用法分析
May 20 PHP
Yii多表联合查询操作详解
Jun 02 PHP
php将文件夹打包成zip文件的简单实现方法
Oct 04 PHP
php实现生成带二维码图片并强制下载功能
Feb 24 PHP
PHP里的$_GET数组介绍
Mar 22 PHP
[原创]PHP global全局变量经典应用与注意事项分析【附$GLOBALS用法对比】
Jul 12 PHP
PHP那些琐碎的知识点(整理)
May 20 #PHP
PHP使用xpath解析XML的方法详解
May 20 #PHP
PHP CodeIgniter分页实例及多条件查询解决方案(推荐)
May 20 #PHP
PHP4和PHP5版本下解析XML文档的操作方法实例分析
May 20 #PHP
PHP实现对xml的增删改查操作案例分析
May 19 #PHP
PHP实现对xml进行简单的增删改查(CRUD)操作示例
May 19 #PHP
php简单处理XML数据的方法示例
May 19 #PHP
You might like
用mysql触发器自动更新memcache的实现代码
2009/10/11 PHP
使用phpQuery采集网页的方法
2013/11/13 PHP
yii通过小物件生成view的方法
2016/10/08 PHP
PHP基于新浪IP库获取IP详细地址的方法
2017/05/04 PHP
yii2实现Ueditor百度编辑器的示例代码
2018/11/02 PHP
Laravel 框架控制器 Controller原理与用法实例分析
2020/04/14 PHP
Jquery插件编写简明教程
2014/03/25 Javascript
用javascript读取xml文件读取节点数据
2014/08/12 Javascript
jQuery实现渐变下拉菜单的简单方法
2015/03/11 Javascript
第一次接触神奇的Bootstrap表单
2016/07/27 Javascript
jQuery EasyUI 页面加载等待及页面等待层
2017/02/06 Javascript
利用n 升级工具升级Node.js版本及在mac环境下的坑
2017/02/15 Javascript
用nodeJS搭建本地文件服务器的几种方法小结
2017/03/16 NodeJs
Vue.js简易安装和快速入门(第二课)
2017/10/17 Javascript
layer实现关闭弹出层刷新父界面功能详解
2017/11/15 Javascript
vue2.0 根据状态值进行样式的改变展示方法
2018/03/13 Javascript
Vue 实时监听窗口变化 windowresize的两种方法
2018/11/06 Javascript
Vue动态组件与异步组件实例详解
2019/02/23 Javascript
微信小程序云开发实现云数据库读写权限
2019/05/17 Javascript
javascript实现异形滚动轮播
2019/11/28 Javascript
[46:20]CHAOS vs Alliacne 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/16 DOTA
pyqt4教程之messagebox使用示例分享
2014/03/07 Python
用Python制作简单的钢琴程序的教程
2015/04/01 Python
用Python进行TCP网络编程的教程
2015/04/29 Python
浅谈MySQL中的触发器
2015/05/05 Python
python中异常捕获方法详解
2017/03/03 Python
Python基于回溯法子集树模板解决0-1背包问题实例
2017/09/02 Python
python3.x实现base64加密和解密
2019/03/28 Python
pytz格式化北京时间多出6分钟问题的解决方法
2019/06/21 Python
tensorflow实现测试时读取任意指定的check point的网络参数
2020/01/21 Python
Python 实现RSA加解密文本文件
2020/12/30 Python
幼儿园教师考核制度
2014/02/01 职场文书
羽毛球社团活动总结
2014/06/27 职场文书
大学生入党自传2015
2015/06/26 职场文书
最新的离婚协议书范本!
2019/07/02 职场文书
numpy array找出符合条件的数并赋值的示例代码
2022/06/01 Python