使用表單令牌,一方面可以防止重複提交表單,二方面可以防止黑客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];
}
}
}
如果學號和姓名空項提交,結果如下:
如果姓名爲空提交,結果如下:
學號和姓名都輸入,結果如下:
如果再次重複提交,結果如下: