解决Yii2 启用_csrf验证后POST数据仍提示“您提交的数据无法验证”

一 CSRF 概念

CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。

Yii2 中的CSRF配置

Yii2 默认是启用CSRF令牌验证

配置在main.php中:

代码语言:javascript
复制
'components' => [
        'request' => [
            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
            'enableCookieValidation' => true,
            'cookieValidationKey' => 'cookvalid',
        ],
      …………

若要取消CSRF验证有两种方法

1. 在要取消的控制器中添加:

代码语言:javascript
复制
public $enableCsrfValidation = false;

2. 在配置中取消enableCookieValidation的验证

代码语言:javascript
复制
'components' => [
        'request' => [
            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
            'enableCookieValidation' => false,
            'cookieValidationKey' => 'cookvalid',
        ],
    …………

二 启用CSRF的 POST验证

当启用了csrf后, 所有表单POST提交的数据就会进行验证,在表单中添加CSRF有两种方法

1. 使用Yii挂件生成html

这样会自动生成带有隐藏表单的_csrf

代码语言:javascript
复制
<?php
                    $form = ActiveForm::begin([
                                'id' => 'login-form',
                    ]);
                    ?>
                    <input type="hidden" name="jfinal_token" value="${jfinal_token }" />
                    <div class="form-group">
                        <label for="j_username" class="t">用户名:</label> 
                        <?php echo Html::input('type', 'LoginForm[username]', $model->username, ['class' => 'form-control x319 in', 'placeholder' => 'Username', 'autocomplete' => 'off']); ?>
                    </div>
                    <div class="form-group">
                    &lt;label for=&#34;j_password&#34; class=&#34;t&#34;&gt;密 码:&lt;/label&gt; 
                    &lt;?php echo Html::input(&#39;password&#39;, &#39;LoginForm[password]&#39;, $model-&gt;password, [&#39;class&#39; =&gt; &#39;form-control x319 in&#39;, &#39;placeholder&#39; =&gt; &#39;Password&#39;]); ?&gt;
                &lt;/div&gt;
               
                &lt;?php ActiveForm::end(); ?&gt;</code></pre></div></div><p><strong>2. 手动添加_csrf</strong></p><p>在form表单中手动添加隐藏表单,也适用于ajax的手动添加_csrf</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">&lt;input type=&#34;hidden&#34; value=&#34;&lt;?php echo Yii::$app-&gt;request-&gt;csrfToken; ?&gt;&#34; name=&#34;_csrf&#34; &gt;</code></pre></div></div><h3 id="6ldih" name="%E4%B8%89-%E6%8F%90%E4%BA%A4POST%E6%8F%90%E7%A4%BA%E2%80%9C%E6%82%A8%E6%8F%90%E4%BA%A4%E7%9A%84%E6%95%B0%E6%8D%AE%E6%97%A0%E6%B3%95%E9%AA%8C%E8%AF%81%E2%80%9D">三 提交POST提示“您提交的数据无法验证”</h3><p>使用原生or Yii挂件生成html带有_csrf 表单提交仍然提示“您提交的数据无法验证”</p><p>表单html如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">&lt;div class=&#34;login_form&#34;&gt;
&lt;form id=&#34;login-form&#34; action=&#34;/default/login&#34; method=&#34;post&#34;&gt;
&lt;input type=&#34;hidden&#34; name=&#34;_csrf&#34; value=&#34;bDV6RjFCS0RURzcwCAwKNC5AIyIDdy19BkAOF38YGSkLGCI2UgcEMQ==&#34;&gt;
&lt;div class=&#34;form-group&#34;&gt;
    &lt;label for=&#34;j_username&#34; class=&#34;t&#34;&gt;用户名:&lt;/label&gt; 
     &lt;input type=&#34;type&#34; class=&#34;form-control x319 in&#34; name=&#34;LoginForm[username]&#34; placeholder=&#34;Username&#34; autocomplete=&#34;off&#34;&gt;               
&lt;/div&gt;
&lt;div class=&#34;form-group&#34;&gt;
&lt;label for=&#34;j_password&#34; class=&#34;t&#34;&gt;密 码:&lt;/label&gt; 
      &lt;input type=&#34;password&#34; class=&#34;form-control x319 in&#34; name=&#34;LoginForm[password]&#34; placeholder=&#34;Password&#34;&gt;        
&lt;/div&gt;
 &lt;/form&gt;     

</div>

_csrf 是Yii自动生成,不存在字符串不匹配

后来找到问题:

render的时候使用了exit, 应使用return

*注: render 时也不能用echo 或 die()

解决办法:

代码语言:javascript
复制
return this-&gt;render(&#39;action&#39;,[&#39;t&#39;=&gt;t,'text'=>text]);</code></pre></div></div><p>封装render</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">return this->display([
't' => $t,
'text' => $text,
]);

/**
* render页面
* @param array 当i为array模版为this->action,数据源为$i
* 当i为string,模版为i,数据源为$param
*/
protected function display(i = array(), param = array()) {
$data = array();
if (is_string($i)) {
tpl = i;
data = param;
} else {
data = i;
tpl = this->action->id;
}

   return $this-&gt;render(strtolower($tpl), $data);
}</code></pre></div></div>