描述你的问题
在开启Csrf防御的时候,默认csrf的值只能使用一次,第二次提交就是验证不通过,因为已经使用过了,那么如何做下面这种效果呢?
如图,该页面是Ajax提交请求,那么第一个按钮点击后csrf值失效了,第二次提交失败返回400错误,求解!
怎么让csrf的值可以刷新后用于第二次请求。
不要让我关闭csrf,csrf本来就是为了防御这种请求的。
经过楼下兄弟的指点,现分析如下,
在使用他自带的表单的时候,yiihelpersBaseHtml::beginForm
方法中有判断,如果在一次页面展示中多次调用表单创建,则每次获取的csrf都是一样的,也就是说,同一页面多个表单,都可用的。
我们知道csrf验证是在yiiwebRequest
类中,但是该类在初始化的时候我并没有看到他有检查csrf,所以我们需要知道他是在哪里进行检查的,通过全文搜索,发现在yiiwebController
类的beforeAction
方法中有检查,原型如下:
<code>/** * @inheritdoc */ public function beforeAction($action) { if (parent::beforeAction($action)) { if ($this->enableCsrfValidation && Yii::$app->getErrorHandler()->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) { throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.')); } return true; } return false; }</code>
通过该代码我们知道,在检查csrf是直接使用的yiiwebRequest::validateCsrfToken
方法,在该方法中先通过loadCsrfToken
方法获取csrf,
<code>/** * Loads the CSRF token from cookie or session. * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session * does not have CSRF token. */ protected function loadCsrfToken() { if ($this->enableCsrfCookie) { return $this->getCookies()->getValue($this->csrfParam); } else { return Yii::$app->getSession()->get($this->csrfParam); } }</code>
这个代码很好理解,就是cookie取,没开的话就在session取,然后返回。
然后通过validateCsrfTokenInternal
方法验证csrf token,通过跟踪代码发现,在验证完csrf后并没有删除该csrf,换句话说就是该csrf还是可以用的,只要你页面不刷新,因为页面刷新时在 你布局文件的头部会有个 <?= Html::csrfMetaTags() ?>
用来输出csrf,这时候上一个csrf就会失效的。所以说一个页面不停地AJAX请求,只要不刷新是没问题的。
结论:
在yii中一共有如下几个地方会刷新csrfyiihelpersBaseHtml::beginForm
、Html::csrfMetaTags()
、yiiwebRequest::getCsrfToken
,关键点在yiiwebRequest::getCsrfToken
方法里如楼下兄弟所言,$regenerate
参数也可以控制强制重新生成csrf token.
<code>private $_csrfToken; /** * Returns the token used to perform CSRF validation. * * This token is a masked version of [[rawCsrfToken]] to prevent [BREACH attacks](http://breachattack.com/). * This token may be passed along via a hidden field of an HTML form or an HTTP header value * to support CSRF validation. * @param boolean $regenerate whether to regenerate CSRF token. When this parameter is true, each time * this method is called, a new CSRF token will be generated and persisted (in session or cookie). * @return string the token used to perform CSRF validation. */ public function getCsrfToken($regenerate = false) { if ($this->_csrfToken === null || $regenerate) { if ($regenerate || ($token = $this->loadCsrfToken()) === null) { $token = $this->generateCsrfToken(); } // the mask doesn't need to be very random $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.'; $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, static::CSRF_MASK_LENGTH); // The + sign may be decoded as blank space later, which will fail the validation $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask))); } return $this->_csrfToken; }</code>
该方法在获取csrftoken的时候判断 $this->_csrfToken
是否是空的,按照页面正常加载来说程序一启动肯定是空的,这个值不是存客户端的,这个也不是从服务端获取的,是页面启动后就是空的,只有执行该方法后生成的csrf token才会放入cookie
和session
,也就是说在同一个请求中多次执行该方法,获取的csrf token都是一样的,如果一个新页面请求到该方法就会重新生成,这也是不同页面刷新为何csrf token一直变的原因,所以,csrf token值可多次使用,但是只限当前页面的ajax请求。
我的系统问题是由于我使用的是IIS 10作为Web,又开了Url重写,默认不存在的文件或文件夹都会重写给Yii处理,然而我没有在web目录下放置传说中的favicon.ico
文件,导致有个404,这个是浏览器在后台自动请求的,导致yii输出了一个404,而我不知道,404页面又调用了布局,导致csrf token 被刷新了。很二是吧。。。。。。
描述你的问题
在开启Csrf防御的时候,默认csrf的值只能使用一次,第二次提交就是验证不通过,因为已经使用过了,那么如何做下面这种效果呢?
如图,该页面是Ajax提交请求,那么第一个按钮点击后csrf值失效了,第二次提交失败返回400错误,求解!
怎么让csrf的值可以刷新后用于第二次请求。
不要让我关闭csrf,csrf本来就是为了防御这种请求的。
经过楼下兄弟的指点,现分析如下,
在使用他自带的表单的时候,yiihelpersBaseHtml::beginForm
方法中有判断,如果在一次页面展示中多次调用表单创建,则每次获取的csrf都是一样的,也就是说,同一页面多个表单,都可用的。
我们知道csrf验证是在yiiwebRequest
类中,但是该类在初始化的时候我并没有看到他有检查csrf,所以我们需要知道他是在哪里进行检查的,通过全文搜索,发现在yiiwebController
类的beforeAction
方法中有检查,原型如下:
<code>/** * @inheritdoc */ public function beforeAction($action) { if (parent::beforeAction($action)) { if ($this->enableCsrfValidation && Yii::$app->getErrorHandler()->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) { throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.')); } return true; } return false; }</code>
通过该代码我们知道,在检查csrf是直接使用的yiiwebRequest::validateCsrfToken
方法,在该方法中先通过loadCsrfToken
方法获取csrf,
<code>/** * Loads the CSRF token from cookie or session. * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session * does not have CSRF token. */ protected function loadCsrfToken() { if ($this->enableCsrfCookie) { return $this->getCookies()->getValue($this->csrfParam); } else { return Yii::$app->getSession()->get($this->csrfParam); } }</code>
这个代码很好理解,就是cookie取,没开的话就在session取,然后返回。
然后通过validateCsrfTokenInternal
方法验证csrf token,通过跟踪代码发现,在验证完csrf后并没有删除该csrf,换句话说就是该csrf还是可以用的,只要你页面不刷新,因为页面刷新时在 你布局文件的头部会有个 <?= Html::csrfMetaTags() ?>
用来输出csrf,这时候上一个csrf就会失效的。所以说一个页面不停地AJAX请求,只要不刷新是没问题的。
结论:
在yii中一共有如下几个地方会刷新csrfyiihelpersBaseHtml::beginForm
、Html::csrfMetaTags()
、yiiwebRequest::getCsrfToken
,关键点在yiiwebRequest::getCsrfToken
方法里如楼下兄弟所言,$regenerate
参数也可以控制强制重新生成csrf token.
<code>private $_csrfToken; /** * Returns the token used to perform CSRF validation. * * This token is a masked version of [[rawCsrfToken]] to prevent [BREACH attacks](http://breachattack.com/). * This token may be passed along via a hidden field of an HTML form or an HTTP header value * to support CSRF validation. * @param boolean $regenerate whether to regenerate CSRF token. When this parameter is true, each time * this method is called, a new CSRF token will be generated and persisted (in session or cookie). * @return string the token used to perform CSRF validation. */ public function getCsrfToken($regenerate = false) { if ($this->_csrfToken === null || $regenerate) { if ($regenerate || ($token = $this->loadCsrfToken()) === null) { $token = $this->generateCsrfToken(); } // the mask doesn't need to be very random $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.'; $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, static::CSRF_MASK_LENGTH); // The + sign may be decoded as blank space later, which will fail the validation $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask))); } return $this->_csrfToken; }</code>
该方法在获取csrftoken的时候判断 $this->_csrfToken
是否是空的,按照页面正常加载来说程序一启动肯定是空的,这个值不是存客户端的,这个也不是从服务端获取的,是页面启动后就是空的,只有执行该方法后生成的csrf token才会放入cookie
和session
,也就是说在同一个请求中多次执行该方法,获取的csrf token都是一样的,如果一个新页面请求到该方法就会重新生成,这也是不同页面刷新为何csrf token一直变的原因,所以,csrf token值可多次使用,但是只限当前页面的ajax请求。
我的系统问题是由于我使用的是IIS 10作为Web,又开了Url重写,默认不存在的文件或文件夹都会重写给Yii处理,然而我没有在web目录下放置传说中的favicon.ico
文件,导致有个404,这个是浏览器在后台自动请求的,导致yii输出了一个404,而我不知道,404页面又调用了布局,导致csrf token 被刷新了。很二是吧。。。。。。
如果你在进行完一次请求没有手动去调用Yii::$app->request->getCsrfToken(true)
的话是不会存在你说的验证不通过的问题的