描述你的問題
在開啟Csrf防禦的時候,預設csrf的值只能使用一次,第二次提交就是驗證不通過,因為已經使用過了,那麼如何做下面這種效果呢?
,該頁面是Ajax提交請求,那麼第一個按鈕點擊後csrf值失效了,第二次提交失敗返回400錯誤,求解!
怎麼讓csrf的值可以重新整理後用於第二次請求。
不要讓我關閉csrf,csrf本來就是為了防禦這種請求的。
經過樓下兄弟的指點,現分析如下,
在使用他內建的表單的時候,\yii\helpers\BaseHtml::beginForm
方法中有判斷,如果在一次頁面展示中多次調用表單建立,則每次擷取的csrf都是一樣的,也就是說,同一頁面多個表單,都可用的。
我們知道csrf驗證是在\yii\web\Request
類中,但是該類在初始化的時候我並沒有看到他有檢查csrf,所以我們需要知道他是在哪裡進行檢查的,通過全文檢索搜尋,發現在\yii\web\Controller
類的beforeAction
方法中有檢查,原型如下:
/** * @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; }
通過該代碼我們知道,在檢查csrf是直接使用的\yii\web\Request::validateCsrfToken
方法,在該方法中先通過loadCsrfToken
方法擷取csrf,
/** * 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); } }
這個代碼很好理解,就是cookie取,沒開的話就在session取,然後返回。
然後通過validateCsrfTokenInternal
方法驗證csrf token,通過跟蹤代碼發現,在驗證完csrf後並沒有刪除該csrf,換句話說就是該csrf還是可以用的,只要你頁面不重新整理,因為頁面重新整理時在 你布局檔案的頭部會有個
用來輸出csrf,這時候上一個csrf就會失效的。所以說一個頁面不停地AJAX請求,只要不重新整理是沒問題的。
結論:
在yii中一共有如下幾個地方會重新整理csrf
\yii\helpers\BaseHtml::beginForm
、Html::csrfMetaTags()
、\yii\web\Request::getCsrfToken
,關鍵點在\yii\web\Request::getCsrfToken
方法裡如樓下兄弟所言,$regenerate
參數也可以控制強制重建csrf token.
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; }
該方法在擷取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本來就是為了防禦這種請求的。
經過樓下兄弟的指點,現分析如下,
在使用他內建的表單的時候,\yii\helpers\BaseHtml::beginForm
方法中有判斷,如果在一次頁面展示中多次調用表單建立,則每次擷取的csrf都是一樣的,也就是說,同一頁面多個表單,都可用的。
我們知道csrf驗證是在\yii\web\Request
類中,但是該類在初始化的時候我並沒有看到他有檢查csrf,所以我們需要知道他是在哪裡進行檢查的,通過全文檢索搜尋,發現在\yii\web\Controller
類的beforeAction
方法中有檢查,原型如下:
/** * @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; }
通過該代碼我們知道,在檢查csrf是直接使用的\yii\web\Request::validateCsrfToken
方法,在該方法中先通過loadCsrfToken
方法擷取csrf,
/** * 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); } }
這個代碼很好理解,就是cookie取,沒開的話就在session取,然後返回。
然後通過validateCsrfTokenInternal
方法驗證csrf token,通過跟蹤代碼發現,在驗證完csrf後並沒有刪除該csrf,換句話說就是該csrf還是可以用的,只要你頁面不重新整理,因為頁面重新整理時在 你布局檔案的頭部會有個
用來輸出csrf,這時候上一個csrf就會失效的。所以說一個頁面不停地AJAX請求,只要不重新整理是沒問題的。
結論:
在yii中一共有如下幾個地方會重新整理csrf
\yii\helpers\BaseHtml::beginForm
、Html::csrfMetaTags()
、\yii\web\Request::getCsrfToken
,關鍵點在\yii\web\Request::getCsrfToken
方法裡如樓下兄弟所言,$regenerate
參數也可以控制強制重建csrf token.
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; }
該方法在擷取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)
的話是不會存在你說的驗證不通過的問題的