一个新数据表单请求可能在刷新浏览器等情况下进行多次操作,导致的结果就是数据被插入多次,这是个很严重的问题。

Yii2的csrf验证机制

  • Yii根据规则生成一个csrf token,存在session或cookie里
  • 把token进行mask加密后生成的maskedToken放到表单页面
  • Post操作提交表单时也把maskedToken一起提交
  • Yii把获取maskedToken进行反mask后得到的token与系统原本的token进行比较
  • 2个token相同,则操作合法,不同,则非法操作

借助csrf防止重复提交

表单重复提交,即相同的表单数据多次已经,包括了相同的maskedToken。如果一个maskedToken只能使用一次,就可以判断第二次以后的请求为非法,变相的防止了重复提交。

控制maskedToken只可使用一次的方法有很多,这篇文章就介绍使用最简单的方式:主动修改csrf token,上一个csrf token生产的maskedToken遇到下一个csrf token时自然无法通验证。

获取token的getCsrfToken方法可以设置为每次都重新生成,只要把参数$regenerate设为true。

1
2
3
4
5
6
7
8
9
10
11
12
13

public function getCsrfToken($regenerate = false)
{
if ($this->_csrfToken === null || $regenerate) {
$token = $this->loadCsrfToken();
if ($regenerate || empty($token)) {
$token = $this->generateCsrfToken();
}
$this->_csrfToken = Yii::$app->security->maskToken($token);
}

return $this->_csrfToken;
}

实现

设置session来保存token

修改项目配置:

1
2
3
4
5
'components' => [
'request' => [
'enableCsrfCookie' => false
]
]

当然也可以继续使用cookie保存,但是建议使用session。

主动修改token

每个请重置token值,在系统csrf验证完成后就马上重置,使之前的maskedToken失效。

1
2
3
4
5
6
7
8
9
10
11
public function beforeAction($action)
{
if (parent::beforeAction($action)) {
if ($this->enableCsrfValidation) {
Yii::$app->getRequest()->getCsrfToken(true);
}
return true;
}

return false;
}

可以把上面自定义的代码放入基控制器中,子控制器就获得了重置token的能力。