服务器迁移后,在 CakePHP3 的请求数据中找不到 '_Token'



编辑:

在提出这个问题后,我获得了一系列新的见解,这些见解教会了我问题是什么,而且它与所描述的服务器迁移毫无关系。

给出的两个答案显示了如何为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');

最新更新