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一些有意思的小区别
Dec 06 PHP
常用的php对象类型判断
Aug 27 PHP
php数组保存文本与文本反编成数组实例
Nov 13 PHP
64位windows系统下安装Memcache缓存
Dec 06 PHP
CI框架中redis缓存相关操作文件示例代码
May 17 PHP
微信公众号开发客服接口实例代码
Oct 21 PHP
基于php(Thinkphp)+jquery 实现ajax多选反选不选删除数据功能
Feb 24 PHP
php获取字符串前几位的实例(substr返回字符串的子串用法)
Mar 08 PHP
三个思路解决laravel上传文件报错:413 Request Entity Too Large问题
Nov 13 PHP
微信JSSDK分享功能图文实例详解
Apr 08 PHP
thinkPHP事务操作简单案例分析
Oct 17 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 高级课程笔记 面向对象
2009/06/21 PHP
PHP的SQL注入实现(测试代码安全不错)
2011/02/27 PHP
PHP微信公众号自动发送红包API
2016/06/01 PHP
ubutu 16.04环境下,PHP与mysql数据库,网页登录验证实例讲解
2017/07/20 PHP
用javascript实现自定义标签
2007/05/08 Javascript
javascript Zifa FormValid 0.1表单验证 代码打包下载
2007/06/08 Javascript
js字符编码函数区别分析
2008/06/05 Javascript
写出更好的JavaScript之undefined篇(上)
2009/11/22 Javascript
javascript getElementsByClassName函数
2010/04/01 Javascript
ExtJS 设置级联菜单的默认值
2010/06/13 Javascript
菜鸟javascript基础整理1
2010/12/06 Javascript
javascript生成随机大小写字母的方法
2014/02/20 Javascript
jquery动态添加删除一行数据示例
2014/06/12 Javascript
JavaScript中实现PHP的打乱数组函数shuffle实例
2014/10/11 Javascript
深入解析JavaScript的闭包机制
2015/10/20 Javascript
JS实现超简单的汉字转拼音功能示例
2016/12/22 Javascript
es6学习笔记之Async函数的使用示例
2017/05/11 Javascript
详解Vue源码学习之callHook钩子函数
2018/07/25 Javascript
详解关于React-Router4.0跳转不置顶解决方案
2019/05/10 Javascript
原生JavaScript之es6中Class的用法分析
2020/02/23 Javascript
解决vue一个页面中复用同一个echarts组件的问题
2020/07/19 Javascript
详解Python多线程
2016/11/14 Python
python机器学习理论与实战(五)支持向量机
2018/01/19 Python
python 爬虫 批量获取代理ip的实例代码
2018/05/22 Python
python利用ffmpeg进行录制屏幕的方法
2019/01/10 Python
Python基于滑动平均思想实现缺失数据填充的方法
2019/02/21 Python
详解python配置虚拟环境
2019/04/08 Python
解决Tensorboard可视化错误:不显示数据 No scalar data was found
2020/02/15 Python
Python xlrd excel文件操作代码实例
2020/03/10 Python
洲际酒店集团美国官网:IHG美国
2017/11/16 全球购物
业务经理岗位职责
2013/11/11 职场文书
财务会计专业个人求职信范本
2014/01/08 职场文书
综合实践教学反思
2014/01/31 职场文书
教师党员公开承诺事项
2014/05/28 职场文书
工作态度不端正检讨书
2014/10/04 职场文书
婚宴来宾致辞
2015/07/28 职场文书