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的ob_start();控制您的浏览器cache!
Nov 25 PHP
php中的实现trim函数代码
Mar 19 PHP
php visitFile()遍历指定文件夹函数
Aug 21 PHP
PHP跨时区(UTC时间)应用解决方案
Jan 11 PHP
php中判断数组是一维,二维,还是多维的解决方法
May 04 PHP
PHP数字和字符串ID互转函数(类似优酷ID)
Jun 30 PHP
如何把php5.3版本升级到php5.4或者php5.5
Jul 31 PHP
PHP5.3新特性小结
Feb 14 PHP
Smarty日期时间操作方法示例
Nov 15 PHP
PHP实现类似题库抽题效果
Aug 16 PHP
PHP常用函数之根据生日计算年龄功能示例
Oct 21 PHP
解决PHPstudy Apache无法启动的问题【亲测有效】
Oct 30 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
Php Cookie的一个使用注意点
2008/11/08 PHP
对squid中refresh_pattern的一些理解和建议
2009/04/17 PHP
处理php自动反斜杠的函数代码
2010/01/05 PHP
php分页示例分享
2014/04/30 PHP
深入解析PHP中foreach语句控制数组循环的用法
2015/11/30 PHP
PHP使用MPDF类生成PDF的方法
2015/12/08 PHP
PHP封装的简单连接MongoDB类示例
2019/02/13 PHP
收集的网上用的ajax之chat.js文件
2007/04/08 Javascript
Jquery实现带动画效果的经典二级导航菜单
2013/03/22 Javascript
JavaScript实现网站访问次数统计代码
2015/08/12 Javascript
深入理解JavaScript单体内置对象
2016/06/06 Javascript
jQuery操作iframe中js函数的方法小结
2016/07/06 Javascript
微信小程序 删除项目工程实现步骤
2016/11/10 Javascript
微信开发之调起摄像头、本地展示图片、上传下载图片实例
2016/12/08 Javascript
js实现微博发布小功能
2017/01/12 Javascript
javascript基本数据类型和转换
2017/03/17 Javascript
angularJs中orderBy筛选以及filter过滤数据的方法
2018/09/30 Javascript
webpack4+express+mongodb+vue实现增删改查的示例
2018/11/08 Javascript
jQuery设置下拉框显示与隐藏效果的方法分析
2019/09/15 jQuery
微信小程序 SOTER 生物认证DEMO 指纹识别功能
2019/12/13 Javascript
解决vue动态路由异步加载import组件,加载不到module的问题
2020/07/26 Javascript
详解vue之自行实现派发与广播(dispatch与broadcast)
2021/01/19 Vue.js
python在命令行下使用google翻译(带语音)
2014/01/16 Python
python中文分词,使用结巴分词对python进行分词(实例讲解)
2017/11/14 Python
Python自定义线程类简单示例
2018/03/23 Python
python 将print输出的内容保存到txt文件中
2018/07/17 Python
python3结合openpyxl库实现excel操作的实例代码
2018/09/11 Python
用CSS3将你的设计带入下个高度
2009/08/08 HTML / CSS
无需JS和jQuery代码实现CSS3鼠标浮动放大图片
2016/11/21 HTML / CSS
css3实现元素环绕中心点布局的方法示例
2019/01/15 HTML / CSS
工商管理系学生的自我评价分享
2013/11/29 职场文书
歌唱比赛获奖感言
2014/01/21 职场文书
2014年寒假社会实践活动心得体会
2014/04/07 职场文书
大学生个人总结范文
2015/02/15 职场文书
多人股份制合作协议书
2016/03/19 职场文书
Pytorch使用shuffle打乱数据的操作
2021/05/20 Python