使用CORS过滤器阻止Ajax请求

时间:2019-04-26 10:34:39

标签: php yii2 cors yii2-api

我试图了解CORS过滤器的工作原理,以及如何从中受益,尤其是在我们谈论Origin选项时。

我正在一个项目中,我需要为客户端提供访问权限,以便使用Ajax请求将线索添加到API中。

我正在使用yii\rest\Controller作为控制器。我已经有一个可用的API,并且有一个基本控制器,可以在其中设置行为和其他常用方法,并从该基本控制器扩展所有控制器。

基本控制器的代码在下面

class ApiController extends yii\rest\Controller
{
    /**
     * @var array
     */
    protected $_logs = [];

    /**
     * Implemented CORS and HTTP Basic Auth for API
     *
     * @return string
     */
    public function behaviors()
    {
        $behaviors = parent::behaviors();
        // remove authentication filter
        $auth = $behaviors['authenticator'];

        // remove authentication filter necessary because we need to
        // add CORS filter and it should be added after the CORS
        unset($behaviors['authenticator']);

        // add CORS filter
        $behaviors['corsFilter'] = [
            'class' => Cors::class,
            'cors' => [
                'Origin' => ['http://www.my-sms-business.local', 'http://localhost'],
                'Access-Control-Request-Method' => ['GET', 'POST'],
            ],
        ];

        // re-add authentication filter
        $behaviors['authenticator'] = $auth;
        // avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
        $behaviors['authenticator']['except'] = ['options'];

        return $behaviors;
    }
}

如您所见,我在http://localhost中添加了用Origin定义的CORS,现在按照Yii的要求,它应该允许来自这些域的请求并且应该可以正常工作,是的,它可以正常工作当我使用带有表单和JavaScript代码的简单HTML页面将销售线索提交给API时,代码如下所示。

$(document).ready(function() {
  $("#my-form").submit(function(e) {
    e.preventDefault();
    //serialize form input data
    let data = $(this).serialize();
    //get the form action
    let url = $(this).attr('action');

    $.ajax({
      url: url,
      method: 'POST',
      data: data,
      success: function(data) {
        if (data.status == 200) {
          alert(data.message);
        } else {
          alert("An error occoured see details,\n " + data.message.join(
            "\n"));
        }
      },
    })
    return false;
  });
});
<form name="my-form" id="my-form" method="POST" action="http://www.my-sms-business.local/api/v1/lead/add?access-token=MY-ACCESS-TOKEN">
  <label>Phone</label>
  <input type="text" name="phone_number" id="phone_number" />
  <label>Campaign Id</label>
  <input type="text" name="campaign_id" />
  <label>user ID</label>
  <input type="text" name="user_id" />
  <label>name</label>
  <input type="text" name="name" />

  <input type="submit" value="Submit" />
</form>

当我通过ajax调用的网址中的查询字符串发送LeadController时,QueryParamAuth使用access-token

class LeadController extends ApiController
{

    /**
     * @return mixed
     */
    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['authenticator'] = [
            'class' => QueryParamAuth::class,
        ];

        return $behaviors;
    }

    /**
     * Add the subscriber to the campaign
     *
     * @return mixed
     */
    public function actionAdd()
    {
        if (Yii::$app->request->isPost) {
            //start db transaction
            $transaction = Yii::$app->db->beginTransaction();

            //post data
            $post['SubscribersForm'] = Yii::$app->request->post();
            $model = new SubscribersForm();
            $model->active = SubscribersForm::ACTIVE_YES;
            $model->type = SubscribersForm::TYPE_SINGLE;

            try {
                if ($model->load($post)) {
                    if ($model->validate()) {
                        //if subscriber already exists in the database
                        $subscriberExists = Subscriber::findOne(['phone_number' => $model->phone_number]) !== null;

                        // call add subscriber so that the subscriber is added to the
                        // specified campaign and also to other campaigns under the
                        // same number the current campaign is associated to.
                        $model->saveSubscribers();

                        //commit the transaction
                        $transaction->commit();

                        if ($subscriberExists) {
                            return $this->formatMessage(['The Phone exsts in our database, we have added the number to the campaign that was provided.'], 200);
                        } else {
                            return $this->formatMessage(['Lead added successfully'], 200);
                        }

                    } else {
                        return $this->formatMessage($model->getErrorSummary(true), 401);
                    }
                }
            } catch (Exception $ex) {

                //roll back the transaction
                $transaction->rollBack();

                //return the error response
                return $this->formatMessage([$ex->getMessage()], $ex->getCode());
            }
        }

    }
}

现在,我想测试一下,如果我从http://localhost中删除了Origin,它应该不允许该来源的请求。当我删除它并点击表单上的Submit按钮发送ajax调用时,我看到控制台抛出警告

  

跨源请求被阻止:同源策略禁止阅读   位于的远程资源   http://www.my-sms-business.local/api/v1/lead/add?access-token=MY-ACCESS-TOKEN。   (原因:CORS标头“ Access-Control-Allow-Origin”缺失)。

并且到目前为止,ajax调用的成功回调中的警报不会触发。

但是,当我单击生成的请求并查看详细信息 BOOM 时,它仍会转到actionAdd()中的LeadController,并尝试添加铅?见下图

enter image description here

它是如何工作的?我不这样认为,因为我认为如果Origin中不存在允许的域,则请求将终止。当前,它的行为方式已成为这种方法的无用选项。

那我在哪里做错了?我缺少什么配置。我们计划在网站的前端提供一个选项,用户可以在其中生成他们的API密钥,并将其域添加到白名单中,从那里他们将发送添加潜在客户的请求。

更新

令我惊讶的是,我使用HttpBasicAuth并使用beforeSend通过ajax调用添加标题,如下所示

beforeSend: function (xhr) {
    xhr.setRequestHeader("Authorization",
        "Basic " + btoa('MY_ACCESS_TOKEN:'));
    xhr.setRequestHeader("Content-Type",
        "application/x-www-form-urlencoded");
},

,然后从我的QueryParamAuth中删除LeadController。并将behaviors()中的ApiController方法更新为以下内容

public function behaviors()
{
    $behaviors = parent::behaviors();
    // remove authentication filter
    $auth = $behaviors['authenticator'];

    // remove authentication filter necessary because we need to
    // add CORS filter and it should be added after the CORS
    unset($behaviors['authenticator']);

    // add CORS filter
    $behaviors['corsFilter'] = [
        'class' => Cors::class,
        'cors' => [
            'Origin' => ['http://www.my-sms-business.local', 'http://localhost'],
            'Access-Control-Request-Method' => ['GET', 'POST', 'OPTIONS'],
            'Access-Control-Request-Headers' => ['Authorization'],
            'Access-Control-Allow-Credentials' => true,

        ],
    ];

    // re-add authentication filter
    $behaviors['authenticator'] = [
        'class' => HttpBasicAuth::class,
    ];
    // avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
    $behaviors['authenticator']['except'] = ['OPTIONS'];

    return $behaviors;
}

如果我从我的白名单中删除了一个域,或者它没有添加线索,它将按预期开始工作,因为OPTIONS请求是在实际请求发送之前发送的,而在失败时它不会启动POST请求。

这是什么意思,为什么使用QueryParamAuth时即使域不在白名单中也要继续执行该操作?

0 个答案:

没有答案