编辑:
在提出这个问题后,我获得了一系列新的见解,这些见解教会了我问题是什么,而且它与所描述的服务器迁移毫无关系。
给出的两个答案显示了如何为CakePHP 2和3"修复"这一问题,但请记住,这可能会带来安全风险。CSRF组件是一个重要的安全功能,不应轻易禁用。
原问题:
我将我的CakePHP 3项目从笔记本电脑上的XAMPP迁移到服务器上的XAMP。自从我激活安全组件后,蛋糕就给我一个错误。它就在这里,直接从错误日志:
2016-05-21 20:32:01 Error: [CakeControllerExceptionAuthSecurityException] '_Token' was not found in request data.
Request URL: /Users/addUser
Referer URL: http://localhost/users/add_user
Stack Trace:
#0 C:xampphtdocsvendorcakephpcakephpsrcControllerComponentSecurityComponent.php(324): CakeControllerComponentSecurityComponent->_validToken(Object(AppControllerUsersController))
#1 C:xampphtdocsvendorcakephpcakephpsrcControllerComponentSecurityComponent.php(130): CakeControllerComponentSecurityComponent->_validatePost(Object(AppControllerUsersController))
#2 C:xampphtdocsvendorcakephpcakephpsrcEventEventManager.php(386): CakeControllerComponentSecurityComponent->startup(Object(CakeEventEvent))
#3 C:xampphtdocsvendorcakephpcakephpsrcEventEventManager.php(356): CakeEventEventManager->_callListener(Array, Object(CakeEventEvent))
#4 C:xampphtdocsvendorcakephpcakephpsrcEventEventDispatcherTrait.php(78): CakeEventEventManager->dispatch(Object(CakeEventEvent))
#5 C:xampphtdocsvendorcakephpcakephpsrcControllerController.php(495): CakeControllerController->dispatchEvent('Controller.star...')
#6 C:xampphtdocsvendorcakephpcakephpsrcRoutingDispatcher.php(109): CakeControllerController->startupProcess()
#7 C:xampphtdocsvendorcakephpcakephpsrcRoutingDispatcher.php(87): CakeRoutingDispatcher->_invoke(Object(AppControllerUsersController))
#8 C:xampphtdocswebrootindex.php(37): CakeRoutingDispatcher->dispatch(Object(CakeNetworkRequest), Object(CakeNetworkResponse))
#9 {main}
我在StackOverflow上发现了CakePHP安全组件的黑名单登录(data[Token][key]字段未生成),但没有其他导致我问题的相关信息。在我的Appcontroller中:
public function initialize()
{
parent::initialize();
$this->loadComponent('Security');
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
错误与_TOKEN
有关。当我们创建一个CakePHP表单,然后基于输入字段,CakePHP生成名为_TOKEN
的隐藏字段。
例如:
<?= $this->Form->create(false, [
'id' => "ajaxForm",
'url' => [
'controller' => 'TPCalls',
'action' => 'add'
],
'class'=> "addUpdateDeleteEventForm"
]);
?>
<?= $this->Form->input('id', ['label' => false]); ?>
<?= $this->Form->input('start', ['label' => false]); ?>
<?= $this->Form->input('end', ['label' => false]); ?>
<?= $this->Form->input('title', ['label' => false]); ?>
<?= $this->Form->hidden('ADD', ['value' => 'true']); ?>
<?= $this->Form->end(); ?>
现在,当检查HTML:时,您应该会在表单中看到_TOKEN值
<input type="hidden" name="_Token[fields]" autocomplete="off" value="---HASH---">
如果没有任何可见字段,则_Token将为空。如果您需要有不可见的字段,那么只需在窗体或字段上添加一个隐藏类。
无论如何,回到主要问题上来。该错误是由_TOKEN
字段不存在引起的。在上面的例子中,我会在进行Ajax调用之前序列化我的表单。
//serializing the form
var ajaxdata = $("#ajaxForm").serializeArray();
//ajax
$.ajax({
url:$("#ajaxForm").attr("action"),
type:"POST",
beforeSend: function(xhr){
xhr.setRequestHeader("X-CSRF-Token", $('[name="_csrfToken"]').val());
},
data:ajaxdata,
dataType: "json",
success:function(response) {
console.log(response);
},
error: function(response) {
console.error(response.message, response.title);
}
});
请注意,在ajax中,我使用Cakephp表单中的URL,而不是在ajax中对其进行硬编码。通过这种方式,它将使用cakehp-url-helper。
在@Invincible评论后编辑
禁用csrf和安全组件时要小心,它们可以防止csrf和表单篡改、强制ssl、http方法等https://book.cakephp.org/3.0/en/controllers/components/security.html.
这个答案只显示如何禁用它们,以防您确定不需要它们来进行请求。
原始答案
在ajax请求的情况下,您可以为该特定操作禁用安全组件(相当于在cake2.x中使该操作解锁)
将此代码放入控制器的beforeFilter
$actions = [
'action1',
'action2'
];
if (in_array($this->request->params['action'], $actions)) {
// for csrf
$this->eventManager()->off($this->Csrf);
// for security component
$this->Security->config('unlockedActions', $actions);
}
禁用csrf组件http://book.cakephp.org/3.0/en/controllers/components/csrf.html#disabling-用于特定操作的csrf组件
禁用安全组件http://book.cakephp.org/3.0/en/controllers/components/security.html#disabling-特定操作的安全组件
UPDATE:此外,确保您没有忘记echo $this->Form->end();
,因为它添加了所有必要的令牌。原始答案如下。
更新:您在提交通过new CakeViewViewBuilder()
单独创建的表单时也可能遇到此问题
正确的答案确实是投入一些时间来更新代码,使其符合安全组件指南。禁用组件或解锁特定操作是一种变通方法,而不是解决方案。
关于_Token
的一些不那么明显的事情。
先决条件:我有一个占位符<form>
,用于构建重复的、几乎相同的AJAX请求(一个字段在javascript循环中不断更新并重新提交;不要问为什么)。
-
_Token
链接到表单操作URL。你不能让你的占位符表单指向例如javascript:;
,并且有实际的请求,用它的令牌签名,去其他端点 -
_Token
可重复使用。可以发出用同一令牌签名的多个请求(即反复提交同一表格),这不像我认为的那样是一次性令牌
所以我所做的是将不同的信息放入type="text"
输入和display:none
输入,而不是使用type="hidden"
输入,这将属于表单篡改预防。然后我serialize()
’d该表单并将其放入jQuery.ajax()
数据属性中。
// update the CSS-hidden type="text" input
document.forms.exampleForm.quantity.value=newValue;
// submit the form
jQuery.ajax({
url: document.forms.exampleForm.action,
type: document.forms.exampleForm.method,
data: jQuery(document.forms.exampleForm).serialize(),
complete: function(jqXHR) {
//
}
});
当然,人们可以选择简单的unlockedActions
路线,但如果你不这样做,你可能会很高兴。
@Invincible的答案很好,但以这种方式应用csrf似乎是应用和维护的噩梦,因为我们的应用程序中已经有大约20个Ajax。
因此,我使用Cakephp3-元素来帮助抽象一些代码。如果你也想抽象csrf令牌,我会把我的代码粘贴在这里供其他人参考。
这是代码:
元素:csrf_ajax_element.ctp
<?=
$this->Form->create(false, [
"id" => $name . "Form",
"url" => $url,
]);
?>
<? if(isset($params)): ?>
<? foreach($params as $param) : ?>
<?= $this->Form->input($param, ['label' => false, 'style' => 'display:none;']); ?>
<? endforeach; ?>
<? endif; ?>
<?= $this->Form->end(); ?>
<script type="text/javascript">
var csrfName = '<?=$name?>';
var url = '<?= $this->Url->build($url) ?>';
var csrf = { };
$.each($('#'+csrfName+'Form').serializeArray(), function() {csrf[this.name] = this.value;});
$("#"+csrfName).data('csrf', csrf);
$("#"+csrfName).data('url', url);
</script>
要在页面上添加ajax,请执行以下操作:
some_page.ctp
<!-- At the top -->
<input id="myAjaxCsrfToken" type="hidden" data-csrf="" data-url="" />
<?= $this->element('csrf_ajax_element',
[
"name" => "myAjaxCsrfToken",
"params" => ['year'],
"url" => ["controller" => "Api", "action" => "myAjax", "_method" => "POST" ]
])
?>
<!-- When you need to use the ajax -->
<script type="text/javascript">
$.ajax({
url: $("#myAjaxCsrfToken").data('url'),
type: 'POST',
data: $.extend(
$("#myAjaxCsrfToken").data('csrf'),
{ year: 2019 }
),
complete: function() {
// things
}
});
</script>
注意:在上面,year
是一个自定义参数,需要与token
参数一起传递给ajax,如果不这样做,cakephp将输出安全错误。
我也遭受了同样的事情,但解决了
cakephp2.10.2
$this->Security->unlockedActions = array('action1', 'action2');