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 相关文章推荐
来自phpguru得Php Cache类源码
Apr 15 PHP
mysql下创建字段并设置主键的php代码
May 16 PHP
php eval函数用法 PHP中eval()函数小技巧
Oct 31 PHP
析构函数与php的垃圾回收机制详解
Oct 28 PHP
php中apc缓存使用示例
Dec 25 PHP
PHP指定截取字符串中的中英文或数字字符的实例分享
Mar 18 PHP
Android App中DrawerLayout抽屉效果的菜单编写实例
Mar 21 PHP
PHP实现登陆表单提交CSRF及验证码
Jan 24 PHP
php smtp实现发送邮件功能
Jun 22 PHP
php中文乱码问题的终极解决方案汇总
Aug 01 PHP
php实现支持中文的文件下载功能示例
Aug 30 PHP
PHP的PDO事务与自动提交
Jan 24 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
memcached 和 mysql 主从环境下php开发代码详解
2010/05/16 PHP
thinkphp3.0 模板中函数的使用
2012/11/13 PHP
php实现过滤表单提交中html标签的方法
2014/10/17 PHP
PHP实现简单爬虫的方法
2015/07/29 PHP
php源码之将图片转化为data/base64数据流实例详解
2016/11/27 PHP
Yii1.1中通过Sql查询进行的分页操作方法
2017/03/16 PHP
浅谈PHP实现大流量下抢购方案
2017/12/15 PHP
zeroclipboard复制到剪切板的flash
2010/08/04 Javascript
js切换光标示例代码
2013/10/10 Javascript
jquery Tab效果和动态加载的简单实例
2013/12/11 Javascript
告诉你什么是javascript的回调函数
2014/09/04 Javascript
javascript执行环境及作用域详解
2016/05/05 Javascript
JavaScript中附件预览功能实现详解(推荐)
2017/08/15 Javascript
Vue组件之Tooltip的示例代码
2017/10/18 Javascript
利用angular、react和vue实现相同的面试题组件
2018/02/19 Javascript
基于Vuex无法观察到值变化的解决方法
2018/03/01 Javascript
JS 实现获取验证码 倒计时功能
2018/10/29 Javascript
IE8中jQuery.load()加载页面不显示的原因
2018/11/15 jQuery
微信小程序实现文件、图片上传功能
2020/08/18 Javascript
Python使用matplotlib绘制正弦和余弦曲线的方法示例
2018/01/06 Python
Python使用matplotlib绘制余弦的散点图示例
2018/03/14 Python
Python设计模式之简单工厂模式实例详解
2019/01/22 Python
Python3.5以上版本lxml导入etree报错的解决方案
2019/06/26 Python
Pycharm+Python+PyQt5使用详解
2019/09/25 Python
Python如何将字符串转换为日期
2020/07/31 Python
大学本科毕业生的自我鉴定
2013/11/26 职场文书
我未来的职业规划范文
2014/01/11 职场文书
《要下雨了》教学反思
2014/02/17 职场文书
学校三八妇女节活动情况总结
2014/03/09 职场文书
离婚协议书的书写要求
2014/09/17 职场文书
个人遵守党的政治纪律情况对照检查材料
2014/09/26 职场文书
2015年秋季灭鼠工作总结
2015/07/27 职场文书
优秀党员先进事迹材料2016
2016/02/29 职场文书
调解协议书范本
2016/03/21 职场文书
SpringBoot 整合mongoDB并自定义连接池的示例代码
2022/02/28 MongoDB
解决Python保存文件名太长OSError: [Errno 36] File name too long
2022/05/11 Python