1. 什么是表单token?
Token(人家就叫这名,记住就行)是服务端生成的一串字符串,以作客户端进行请求的一个令牌和依据。
2. 表单token用来干什么?
第一:防止提交表单是为外部提交。也叫(跨站点请求伪造)。
第二:防止表单重复提交,造成数据错乱。
3. 表单token的实现原理是什么?
在用户进入任何一个表单页面的时候,服务器端就会生成一段针对该用户的唯一token码,同时该token码会保存到该用户session_id下session数据中。这个token码就在表单的隐藏域中。在用户提交该表单的时候会跟随其他数据一并提交。后台会进行数据验证,包括什么用户名验证(不为空,不能包含中文等),密码验证(不能全为数字,不能少于10位等),还有其他乱其八糟的验证。其中就包括token验证(与该用户session_id下储存的token码进行比对),如果token为空或者不等于当前session中存储的token码,就说明该表单为外部提交表单或者是重复提交表单,将不在进行数据库操作。如果token和其他数据都验证通过了,(先把session中的token码清空)进行数据库操作,提交数据。
注意:不论token码验证失败还是成功,都必须进行清空。如果验证失败了没有清空session中的token,如果你的验证规则不好(token不唯一),可能多试几次就会通过了。如果验证成功了,页面没有进行跳转,你也没清空session中的token,多点几次提交,这些数据也都会提交。
这里我那TP5框架里的表单验证来举例。
商品分类表单提交页面:
表单后台代码(只贴一部分):
<div class="ncap-form-default">
<div class="bot">
<input type="hidden" name="id" value="{$Info.id}">
<input type="hidden" name="__token__" value="{$Request.token}" />
<a href="JavaScript:void(0);" class="ncap-btn-big ncap-btn-green" onClick="ajax_submit_form('addEditCoordinatesForm','{:U('Coordinates/addEditCoordinates?is_ajax=1')}');">确认提交a>
div>
div>
form>
后台代码:
if((I('is_ajax')==1) && IS_POST){
$data = I('post.');
$validate = \think\Loader::validate('Goods');
if (!$validate->batch()->check($data)) {//数据验证
$error = $validate->getError();
$error_msg = array_values($error);
$return_arr = array(
'status' => -1,
'msg' => $error_msg[0],
'data' => $error,
);
$this->ajaxReturn($return_arr);
}
这段代码是后台调取验证规则的,以下是我的验证规则:
namespace app\admin\validate;
use think\Validate;
class Goods extends Validate
{
// 验证规则
protected $rule = [
['Goods_name','require|unique:goods','商品名称必填|商品名称重复'],
['Goods_sn', 'unique:goods', '商品货号重复'], // 更多 内置规则 http://www.kancloud.cn/manual/thinkphp5/129356
['shop_price','regex:\d{1,10}(\.\d{1,2})?$','本店售价格式不对。'],
['market_price','regex:\d{1,10}(\.\d{1,2})?$','市场价格式不对。'],
['weight','regex:\d{1,10}(\.\d{1,2})?$','重量格式不对。'],
['exchange_integral','checkExchangeIntegral','积分抵扣金额不能超过商品总额'],
['__token__','token','正在拼了死命的加载中······'],
];
验证规则数组中的一个值是要验证的字段,第二个值时要调用的规则(可以自定义规则),第三个是提示信息。TP5的官方手册的写法让人困惑感觉好像是token验证是追加到了某个字段(如name字段)的后边,好像必须要依附于某个字段才能进行验证。
先不提官方文档。我这样写是标准格式,绝对没有问题的。
注意:这里需要注意一个细节,观察token源码,发现生成token是在Request类里:
/**
* 生成请求令牌
* @access public
* @param string $name 令牌名称
* @param mixed $type 令牌生成方法
* @return string
*/
public function token($name = '__token__', $type = 'md5')
{
$type = is_callable($type) ? $type : 'md5';
$token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']);
if ($this->isAjax()) {
header($name . ': ' . $token);
}
Session::set($name, $token);
return $token;
}
然后验证token是在Validate类里:
/**
* 验证表单令牌
* @access protected
* @param mixed $value 字段值
* @param mixed $rule 验证规则
* @param array $data 数据
* @return bool
*/
protected function token($value, $rule, $data)
{
$rule = !empty($rule) ? $rule : '__token__';
if (!isset($data[$rule]) || !Session::has($rule)) {
// 令牌数据无效
return false;
}
// 令牌验证
if (isset($data[$rule]) && Session::get($rule) === $data[$rule]) {
// 防止重复提交
Session::delete($rule); // 验证完成销毁session
return true;
}
// 开启TOKEN重置
Session::delete($rule);
return false;
}
而每次验证完token,都会进行清空,所以所有的其他验证规则到要放到token验证之前,不然token验证完成了,而其他有的字段验证未通过,你的表单是需要重新填写的,否则会永远提交不上。放到最后边,即使其他字段验证失败,从新修改那个字段继续提交即可,不需要重新填写表单。哒哒······结束啦。