欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  php教程

解决表单令牌验证,ajax无刷新多次提交暂不能满足问题

程序员文章站 2024-02-18 16:46:16
...
ajax请求,create进行了令牌验证并销毁了令牌session,再次ajax提交请求后,无法通过令牌验证,有两个解决办法:
1,在Model.class.php核心类中,添加是否销毁令牌session的参数,当是ajax请求时则不销毁session【不太安全】
2,在create销毁session后,再生成新的令牌,将新生成的令牌返回给ajax回调函数,在回调函数中修改页面的令牌,这样再次请求时使用的是最新令牌
此处分享下自己查看思路,如存在何问题望指出:

问题:
表单普通提交使用了令牌验证,当网络慢的时候避免重复提交保存多余数据问题,但是使用ajax提交第二次会报错
原因:
使用ajax将令牌提交后,在控制器中使用create方法验证数据的时候,将会销毁此次令牌,并通过验证保存数据,
但无法产生新的令牌,页面再次提交旧令牌,在create方法验证时则无法通过,所以报错。为什么ajax提交无法产生新令牌?Create方法又是如何验证令牌并销毁令牌的呢?令牌是存贮在哪里呢?带着这些问题,查看源码
分析:
令牌的产生:
Thinkphp框架从程序的开始,请求——控制器处理——页面输出,使用了大量行为,使用标签【类似事件】(一个标签可以带有很多行为)方式执行程序。
当开启令牌的时候,页面就会生成一个name为__hash__的隐藏域,但是我们只使用了form标签,没有添加这个隐藏域,那肯定是系统给添加的,什么时候会添加呢,很大的可能是通过模板替换时。查看thinkphp的行为,里面有一个TokenBuildBehavior的创建令牌的行为,什么时候调用了这个行为呢?因为thinkphp系统行为的执行是在tags中配置的,查看果然是在view_filter这个事件时执行的,从单词可以看出是与视图类很相关的。产看核心 View.class.php
,可以找到是在fetch方法中,即执行display时执行的,确定是模板输出时生成的令牌。
打开创建令牌的类,可以看到令牌的设置,名字 加密等。 生成的令牌是1、请求页面url md5加密后连下划线连接
2、时间加密【这是页面上看到的】。两个加密串分别作为键和值保存在$_session[‘__hash__’]中【如果你没有修改令牌名称】。生成好的令牌,系统会判断是否开启令牌,是否在页面写__hash__这个名称的隐藏域,如果没写则会只能地通过正则匹配form表单来添加令牌
protected $options = array(
'TOKEN_ON' => false, // 开启令牌验证
'TOKEN_NAME' => '__hash__', // 令牌验证的表单隐藏字段名称
'TOKEN_TYPE' => 'md5', // 令牌验证哈希规则
'TOKEN_RESET' => true, // 令牌错误后是否重置
);
'view_filter' => array(
'ContentReplace', // 模板输出替换
'TokenBuild', // 表单令牌
'WriteHtmlCache', // 写入静态缓存
'ShowRuntime', // 运行时间显示
),
令牌的验证:
令牌产生了,那么怎么防止重复提交呢?
Thinkphp使用create方法做了很多工作,其中包括令牌验证,可以通过核心类model.class.php文件查看,但是create方法并不进行真正的写数据库操作,保存工作交给了add等方法。通过 $data = $_POST;将提交的数据交给了create方法,所以使用这个方法时可以不用手动传递参数。将提交的数据进行字符串分隔,分隔好的数据包括了键和值两部分,如果session中保存的数据和提交的数据相符则通过验证并销毁这个session,如果不相符,则返回false并销毁【如果重置令牌为开启状态】。只有通过了create方法的验证才会调用真正的保存操作add,所以如果在还未保存数据时进行了第二次提交,那么session已经消除,验证不通过报令牌错误。
此时就清楚了,ajax请求后,在控制器进行了create验证,销毁了session但是,没有机会产生新的令牌,因为产生新令牌是在输出页面的时候调用的创建令牌的行为。
解决办法:
1,在Model.class.php核心类中,添加是否销毁令牌session的参数,当是ajax请求时则不销毁session【不太安全】2,在create销毁session后,再生成新的令牌,将新生成的令牌返回给ajax回调函数,在回调函数中修改页面的令牌,这样再次请求时使用的是最新令牌

第一种太简单,此处详述第二种办法。
代码参见:
创建只生成令牌不进行模板替换的行为类【最好继承系统的令牌创建行为类,避免自己再次定义一些变量等】,创建自己的行为标签【在tags中添加'create_token' => array('TokenOnly'),】,在create方法验证完了后调用创建令牌的行为类,并将产生的令牌返回给ajax回调函数
$token='';
tag(create_token,$token);
echo json_encode(array('flag'=>"ok",'token'=>$token));
在视图页面进行js修改页面的__hash__隐藏域的值,变为最新令牌
$(__hash__).val(data.token);
就此完成
对于只创建令牌的行为类,继承系统令牌创建行为类,去掉带有标签的内容即可,代码参考:
defined('THINK_PATH') or exit();
/**
* 只返回生成的表单令牌
*/
class TokenOnlyBehavior extends TokenBuildBehavior {
public function run(&$content){
$this->buildToken(&$content);
}
// 创建表单令牌
private function buildToken(&$content) {
$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;
}
$content = $tokenKey.'_'.$tokenValue;
}
}

AD:真正免费,域名+虚机+企业邮箱=0元