Yii2 中CSRF的疑問(完結)

來源:互聯網
上載者:User
  1. 描述你的問題
    在開啟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::beginFormHtml::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才會放入cookiesession,也就是說在同一個請求中多次執行該方法,擷取的csrf token都是一樣的,如果一個新頁面請求到該方法就會重建,這也是不同頁面重新整理為何csrf token一直變的原因,所以,csrf token值可多次使用,但是只限當前頁面的ajax請求。

我的系統問題是由於我使用的是IIS 10作為Web,又開了Url重寫,預設不存在的檔案或檔案夾都會重寫給Yii處理,然而我沒有在web目錄下放置傳說中的favicon.ico檔案,導致有個404,這個是瀏覽器在後台自動請求的,導致yii輸出了一個404,而我不知道,404頁面又調用了布局,導致csrf token 被重新整理了。很二是吧。。。。。。

回複內容:

  1. 描述你的問題
    在開啟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::beginFormHtml::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才會放入cookiesession,也就是說在同一個請求中多次執行該方法,擷取的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)的話是不會存在你說的驗證不通過的問題的

  • 相關文章

    聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.