简体   繁体   English

Yii 1.1.17:CSRF令牌验证通过Angular Controller的POST失败

[英]Yii 1.1.17: CSRF token validation fails with POST via Angular Controller

I have an Yii application in which I have a link (outside a form) triggering an Angular controller posting some data. 我有一个Yii应用程序,其中有一个链接(窗体外部)触发一个发布一些数据的Angular控制器。 The problem is that Yii does not validate the CSRF token when that happens. 问题是Yii不会在发生这种情况时验证CSRF令牌。

My raw url looks like this: 我的原始网址看起来像这样:

<a id="yt1" href="#" ng-click="markAllAsRead(23, '1eb4e3ac755e22939a0fc8d5ea0e9bacb453319a')" title="Read All" tooltips-size="small" tooltips="1" class="notification-tool ng-isolate-scope"><i class="fa fa-eye"></i></a>

My Angular controller calls an angular service which looks like this: 我的Angular控制器调用了一个如下所示的Angular服务:

notificationsService.markAllAsRead = function (user_id, csrf) {
        var dataObject = {
            user_id: user_id,
            YII_CSRF_TOKEN: csrf
        };

        $http.post("/api/notifications/readAll", dataObject).success(function (data) {
            return data;
        });
    };

The POST request looks liek this: POST请求看起来像这样: 通过ajax发布请求

If I disable the CSRF validation the call succeeds. 如果我禁用CSRF验证,则调用成功。

Any ideas? 有任何想法吗?

Thanks! 谢谢!

COMPLETE ANSWER: 完整答案:

After some investigation I noticed that the $_POST (also Yii's $request->getPost() ) was actually empty, even though Angular was posting data. 经过一番调查之后,我注意到$_POST (也是Yii的$request->getPost() )实际上是空的,即使Angular正在发布数据。 Reading this answer on stackoverflow it seems that it actually is an issue that has to do with Angular JS and its' default behavior to post as apllication/json (well, maybe not exactly an issue). 阅读关于stackoverflow的答案 ,似乎这实际上是与Angular JS有关的一个问题,它的默认行为是发布为apllication/json (嗯,也许不完全是问题)。 As suggested by the marked answer to this question and based on the suggestion on the linked answer, I ended up overriding the CHttpRequest class from Yii as follows: 正如该问题的标记答案所建议的那样,并基于对链接答案的建议,我最终如下重写了Yii的CHttpRequest类:

class AppRequest extends CHttpRequest
{
    public function validateCsrfToken($event)
    {
        if ($this->getIsPostRequest() ||
            $this->getIsPutRequest() ||
            $this->getIsPatchRequest() ||
            $this->getIsDeleteRequest()
        ) {
            $cookies = $this->getCookies();

            $method = $this->getRequestType();
            switch ($method) {
                case 'POST':
                    if (empty($this->getPost($this->csrfTokenName))) {
                        $input = json_decode(file_get_contents('php://input'), true);;
                        $userToken = $input[$this->csrfTokenName];
                    } else {
                        $userToken = $this->getPost($this->csrfTokenName);
                    }
                    break;
                case 'PUT':
                    if (empty($this->getPut($this->csrfTokenName))) {
                        $input = json_decode(file_get_contents('php://input'), true);;
                        $userToken = $input[$this->csrfTokenName];
                    } else {
                        $userToken = $this->getPut($this->csrfTokenName);
                    }
                    break;
                case 'PATCH':
                    if (empty($this->getPatch($this->csrfTokenName))) {
                        $input = json_decode(file_get_contents('php://input'), true);;
                        $userToken = $input[$this->csrfTokenName];
                    } else {
                        $userToken = $this->getPatch($this->csrfTokenName);
                    }
                    break;
                case 'DELETE':
                    if (empty($this->getDelete($this->csrfTokenName))) {
                        $input = json_decode(file_get_contents('php://input'), true);;
                        $userToken = $input[$this->csrfTokenName];
                    } else {
                        $userToken = $this->getDelete($this->csrfTokenName);
                    }
                    break;
            }

            if (!empty($userToken) && $cookies->contains($this->csrfTokenName)) {
                $cookieToken = $cookies->itemAt($this->csrfTokenName)->value;
                $valid = $cookieToken === $userToken;
            } else
                $valid = false;
            if (!$valid)
                throw new CHttpException(400, Yii::t('yii', 'The CSRF token could not be verified.'));
        }
    }
}

If you will look at CHttpRequest and watch how validation performed, you will understand problem. 如果您查看CHttpRequest并观察验证的执行方式,那么您将了解问题。

public function validateCsrfToken($event)
{
    if ($this->getIsPostRequest() ||
        $this->getIsPutRequest() ||
        $this->getIsPatchRequest() ||
        $this->getIsDeleteRequest())
    {
        $cookies=$this->getCookies();
        $method=$this->getRequestType();
        switch($method)
        {
            case 'POST':
                $userToken=$this->getPost($this->csrfTokenName);
            break;
            case 'PUT':
                $userToken=$this->getPut($this->csrfTokenName);
            break;
            case 'PATCH':
                $userToken=$this->getPatch($this->csrfTokenName);
            break;
            case 'DELETE':
                $userToken=$this->getDelete($this->csrfTokenName);
        }
        if (!empty($userToken) && $cookies->contains($this->csrfTokenName))
        {
            $cookieToken=$cookies->itemAt($this->csrfTokenName)->value;
            $valid=$cookieToken===$userToken;
        }
        else
            $valid = false;
        if (!$valid)
            throw new CHttpException(400,Yii::t('yii','The CSRF token could not be verified.'));
    }
}

So to make CSRF validation work we need 2 conditions: 因此,要使CSRF验证有效,我们需要两个条件:

  1. Client must be eligible to send a request with cookie and accept cookies. 客户必须符合发送带有cookie的请求并接受cookie的资格。

  2. We need to pass token with our request. 我们需要在请求中传递令牌。

2nd condition in your case not working. 您的情况下的第二个条件不起作用。 You'r passing json to request body, and Yii trying to get token from post: 您正在将json传递给请求正文,而Yii试图从帖子中获取令牌:

$userToken=$this->getPost($this->csrfTokenName);

To change this behavior, you need to override CHttpRequest and change config file to use your Request class, eg 要更改此行为,您需要覆盖CHttpRequest并更改配置文件以使用您的Request类,例如

  'components' => array(
    'request' => array(
        'class' => 'application.components.HttpRequest',
        'enableCsrfValidation' => true,
    ),
  ),

Hope this will help with understanding of what's happening when CSRF validation performed. 希望这将有助于了解执行CSRF验证时发生的情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM