我试图了解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
,并尝试添加铅?见下图
它是如何工作的?我不这样认为,因为我认为如果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
时即使域不在白名单中也要继续执行该操作?