Использование фильтра CORS для блокировки запросов Ajax - PullRequest
1 голос
/ 26 апреля 2019

Я пытаюсь понять, как CORS Filter работает и как можно извлечь из него выгоду, особенно когда мы говорим о опции Origin.

IЯ работаю над проектом, где мне нужно предоставить клиенту доступ для добавления потенциальных клиентов в API с помощью запросов Ajax.

Я использую 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;
    }
}

Как вы видите, у меня есть CORS, определенный с http://localhost, добавленным в Origin, теперь, согласно Yii, он должен позволятьзапросы от этих доменов, и он должен работать без сбоев. Да, он работает правильно, когда я использую простую HTML-страницу с формой и кодом javascript для передачи ссылки на 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>

LeadController использует QueryParamAuth при отправке access-token через строку запроса в URL-адресе вызова ajax.

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, он не должен разрешать запрос от этого источника.Когда я удаляю его и нажимаю кнопку «Отправить» в форме для отправки вызова 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 и добавляю заголовки через мой вызов ajax, используя beforeSend, как показано ниже

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;
}

. Он начинает работать, как и ожидалось, если я удаляю домен из белого списка или Origin, он не добавляет лид, как OPTIONS запрос отправляется до того, как будет отправлен фактический запрос, и при сбое он не инициирует запрос POST.

Итак, что же все это значит, почему он переходит к действию, даже если домен нев белый список при использовании QueryParamAuth?.

...