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 最大运行时间 max_execution_time修改方法
Mar 08 PHP
深入php常用函数的使用汇总
Jun 08 PHP
PHP之短标签开启设置
Jun 17 PHP
解析htaccess伪静态的规则
Jun 18 PHP
解析php如何将日志写进syslog
Jun 28 PHP
php selectradio和checkbox默认选择的实现方法详解
Jun 29 PHP
php旋转图片90度的方法
Nov 07 PHP
php实现图片缩放功能类
Dec 18 PHP
phpcms手机内容页面添加上一篇和下一篇
Jun 05 PHP
PHP 中使用ajax时一些常见错误总结整理
Feb 27 PHP
IOS 开发之NSDictionary转换成JSON字符串
Aug 14 PHP
php给数组赋值的实例方法
Sep 26 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
深入探讨:Nginx 502 Bad Gateway错误的解决方法
2013/06/03 PHP
php实现简单的上传进度条
2015/11/17 PHP
php使用scandir()函数扫描指定目录下所有文件示例
2019/06/08 PHP
JavaScript判断两种格式的输入日期的正确性的代码
2007/03/25 Javascript
javascript 限制输入脚本大全
2009/11/03 Javascript
ajax 同步请求和异步请求的差异分析
2011/07/04 Javascript
javascript的函数作用域
2014/11/12 Javascript
nodejs实现HTTPS发起POST请求
2015/04/23 NodeJs
探讨JavaScript标签位置的存放与功能有无关系
2016/01/15 Javascript
基于BootStrap实现局部刷新分页实例代码
2016/08/08 Javascript
Bootstrap Modal遮罩弹出层(完整版)
2016/11/21 Javascript
微信小程序本地缓存数据增删改查实例详解
2017/05/24 Javascript
利用Vue.js实现求职在线之职位查询功能
2017/07/03 Javascript
详解用node搭建简单的静态资源管理器
2017/08/09 Javascript
微信小程序中setInterval的使用方法
2017/09/29 Javascript
使用vue-cli创建项目的图文教程(新手入门篇)
2018/05/02 Javascript
JQuery常用简单动画操作方法回顾与总结
2019/12/07 jQuery
小程序瀑布流组件实现翻页与图片懒加载
2020/05/19 Javascript
对numpy中轴与维度的理解
2018/04/18 Python
python实现监控某个服务 服务崩溃即发送邮件报告
2018/06/21 Python
解决tensorflow训练时内存持续增加并占满的问题
2020/01/19 Python
对Tensorflow中tensorboard日志的生成与显示详解
2020/02/04 Python
Python使用configparser库读取配置文件
2020/02/22 Python
使用python对excel表格处理的一些小功能
2021/01/25 Python
Python自动化测试基础必备知识点总结
2021/02/07 Python
澳大利亚最大的在线美发和美容零售商之一:My Hair Care & Beauty
2019/08/24 全球购物
WatchShop法国:英国排名第一的独立手表零售商
2020/02/17 全球购物
大学生求职工作的自我评价
2014/02/13 职场文书
《威尼斯的小艇》教学反思
2014/02/17 职场文书
房屋继承公证书
2014/04/10 职场文书
《地震中的父与子》教学反思
2014/04/10 职场文书
《鲁班和橹板》教学反思
2014/04/27 职场文书
本科毕业生自荐信
2014/06/02 职场文书
关于做家务的心得体会
2016/01/23 职场文书
Golang二维切片初始化的实现
2021/04/08 Golang
Java生成日期时间存入Mysql数据库的实现方法
2022/03/03 Java/Android