使用表单令牌,一方面可以防止重复提交表单,二方面可以防止黑客CSRF。
表单令牌是在服务器端随机生成的一串字符,保存在session中,并传递给前端页面,当前端数据提交时,一并携带令牌提交给服务器,服务器验证提交的令牌与session中的令牌是否一致,并同时销毁session中的令牌。所以每次请求时的令牌只能使用一次,下次如果使用同样的令牌就会失效,这即阻止了重复提交,也防止了黑客CSRF。
生成表单令牌经常使用以下两种方式:
在前端页面中 嵌入 <input type="hidden" name="__token__" value="{$Request.token}" />
<form id="form1" action="{:url('do_submit')}" method="post">
<div>
<label>学号</label>
<input type="hidden" name="__token__" value="{$Request.token}">
<input type="text" name="no">
</div>
<div>
<label>姓名</label>
<input type="text" name="name">
</div>
<div>
<input type="submit" value="提交">
</div>
</form>
在前端页面嵌入{:token()}
<form id="form1" action="{:url('do_submit')}" method="post">
<div>
<lable>学号</lable>
{:token()}
<input type="text" name="no">
</div>
<div>
<lable>姓名</lable>
<input type="text" name="name">
</div>
<div>
<input type="submit" value="提交">
</div>
</form>
这两种方式结果是一样。{:token()} 最终也生成一个隐藏域,查看网页源代码:
<input type="hidden" name="__token__" value="a205d6c87ab5d2c0997a4586322405af" />
服务器端要做两件事:
1、编写验证器和验证规则
2、使用验证器验证提交的数据
以下是验证器的参考代码:
<?php
namespace app\index\validate;
use think\Validate;
/**
* 验证器
*/
class User extends Validate
{
protected $rule = [
"no" => 'require|token',
"name" => 'require'
];
}
"no" => 'require|token' no 是表单中的文本框name="no"传递来的值,require表示必填, token是验证传过来的表单令牌是否和服务器上的令牌一致,其实与 no 并无半毛钱关系其中,这样写法很让人产生误解,不妨改成以下代码更好理解
<?php
namespace app\index\validate;
use think\Validate;
/**
* 验证器
*/
class User extends Validate
{
protected $rule = [
"no" => 'require',
"name" => 'require',
"__token__" =>'require|token',
];
}
下面是调用验证器的参考代码
<?php
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
public function index()
{
return $this->fetch();
}
public function do_submit()
{
$data = input('post.');
$result = $this->validate($data,'User');
dump($result);
}
}
停牌验证成功,输出true
令牌验证失败:输出"令牌数据无效"
如果在此页面上刷新 ,就会出现以下结果:
表单令牌同样适用于异步提交表单
前端参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="__STATIC__/jquery.min.js"></script>
</head>
<body>
<form id="form1" action="{:url('do_submit')}" method="post">
<div>
<lable>学号</lable>
{:token()}
<input type="text" name="no">
</div>
<div>
<lable>姓名</lable>
<input type="text" name="name">
</div>
<div>
<input type="submit" value="提交">
</div>
</form>
<script>
$(function () {
$('#form1').on('submit',function () {
var data = $(this).serialize()
var url = $(this).attr('action')
$.ajax({
type:'post',
url:url,
data:data,
success:function (ret) {
console.log(ret)
}
})
return false //阻止表单同步提交
})
})
</script>
</body>
</html>
服务器端参考代码:
<?php
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
public function index()
{
return $this->fetch();
}
public function do_submit()
{
$data = input('post.');
$result = $this->validate($data, 'User');
if (true === $result) {
return ['code' => 200, 'message' => '验证成功'];
} else {
return ['code' => 401, 'message' => $result];
}
}
}
如果学号和姓名空项提交,结果如下:
如果姓名为空提交,结果如下:
学号和姓名都输入,结果如下:
如果再次重复提交,结果如下: