可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
When I try to login, the request gets blackholed by teh Security
component. How can I make it work right?
I have a simple login form
<div class="container container-login">
<h2><?php echo __('Login'); ?></h2>
<div class="wrap-form-signin">
<?php
echo $this->Form->create('User', array('action' => 'login', 'class' => 'form-signin'));
echo $this->Form->input('username', array('label' => '', 'placeholder' => __('Email')));
echo $this->Form->input('password', array('label' => '', 'placeholder' => __('Password')));
echo $this->Form->submit(__('Login'));
echo $this->Form->end();
?>
</div>
</div>
The controller action goes like this:
public function login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirectUrl());
} else {
$this->Session->setFlash(__('Username or password is incorrect'), 'default', array(), 'auth');
}
}
}
And the Security
component is included in AppController
public $components = array('Security', ... );
In the error.log I get:
2013-03-29 13:40:58 Error: [BadRequestException] The request has been black-holed
Request URL: /users/login
Stack Trace:
#0 C:\wamp\www\cdx\lib\Cake\Controller\Component\SecurityComponent.php(234): SecurityComponent->blackHole(Object(UsersController), 'auth')
#1 [internal function]: SecurityComponent->startup(Object(UsersController))
#2 C:\wamp\www\cdx\lib\Cake\Utility\ObjectCollection.php(131): call_user_func_array(Array, Array)
#3 [internal function]: ObjectCollection->trigger(Object(CakeEvent))
#4 C:\wamp\www\cdx\lib\Cake\Event\CakeEventManager.php(247): call_user_func(Array, Object(CakeEvent))
#5 C:\wamp\www\cdx\lib\Cake\Controller\Controller.php(670): CakeEventManager->dispatch(Object(CakeEvent))
#6 C:\wamp\www\cdx\lib\Cake\Routing\Dispatcher.php(183): Controller->startupProcess()
#7 C:\wamp\www\cdx\lib\Cake\Routing\Dispatcher.php(161): Dispatcher->_invoke(Object(UsersController), Object(CakeRequest), Object(CakeResponse))
#8 C:\wamp\www\cdx\app\webroot\index.php(92): Dispatcher->dispatch(Object(CakeRequest), Object(CakeResponse))
#9 {main}
How can I find what is making my request go to black hole?
When I tried to use a custom blackhole handler, the type of the error was auth
. But that's all the information I can get
My version of CakePHP is 2.3.1
EDIT:
The login works well without the Security
component. After adding it to the AppController
, the login stops working.
EDIT2: I don't have any data[_Token][key]
fields in the form
EDIT3 THE SOLLUTION:
Someone from my team has overriden the HTMLHelper class and it missed "hiddenblock" in the _tags array, which resulted in missing _Token fields. For details, see mine and thaJeztah's answers along with the comments bellow them
回答1:
In what order did you add your components? The security-component should be put before other components that handle form-data in their startup()
:
"If you are using Security component’s form protection features and other components that process form data in their startup() callbacks, be sure to place Security Component before those components in your $components array."
Security
Because the AuthComponent does handle form data inside the startup()
, I think this applies, so be sure that the SecurityComponent is before the AuthComponent in your $components array;
public $components = array(
'Security',
'Session',
'Auth' => array(
// auth component settings
)
);
Update
The 'final' answer as posted by the OP made clear that this question could not have been answered. as it turned out, somebody in the team made modifications to the HtmlHelper, causing it to not output 'hidden' blocks, and therefore not outputting the CSRF token.
In normal situations, you should never make modifications to the CakePHP Framework files themselves. CakePHP offers ways to override its functionality (including Helpers) without modifying the 'core' CakePHP files.
Why modifying CakePHP files is bad
Consider, for example, a car. What if a mechanic didn't like the design and decided to 'swap' the brake and accelerator pedals?
Of course, the car is still able to drive if you we're aware of this modification. However, without this important information, any other driver would definitely crash (and wonder what the heck just happened!?)
If the default behavior of the framework doesn't fit your needs, extend those classes. Do not modify the framework files themselves (unless there really is no other option). If modifications to the framework are absolutely nescessary, be sure to discuss this with the team and write documentation on the changes that have been made.
Keep in mind that it will no longer be possible to update the framework to a newer version, without applying the same modifications to the updated version as well. Again, if the changes have not been documented, somebody might update CakePHP and destroy your modifications.
Also, if you override or modify CakePHP, make sure that the overrides are compatible with the default behavior of CakePHP and the unit tests of CakePHP still run correctly (or create new Unit Tests for the modifications)
Using 'customised' Helpers in CakePHP
If you need to customise the CakePHP Helpers (or other components), there are various options to do so without modifying the CakePHP files;
1. Extend the Helper
class AwesomeHtmlHelper extends HtmlHelper {
/**
* enhanced tableHeaders method, outputs tableHeaders in a 'thead' tag
*
* {@inheritdoc}
*/
public function tableHeaders(array $names, array $trOptions = null, array $thOptions = null)
{
$output = parent::tableHeaders($names, $trOptions, $thOptions);
return '<thead>' . $output . '</thead>';
}
}
Then, use your Helper the regular way:
echo $this->AwesomeHtml->tableHeaders(array('Date', 'Title', 'Active'));
2. 'Drop-in' replacement - use an alias (CakePHP > 2.3)
Since CakePHP 2.3 its possible to use an alias for a Helper. This functionality can be used in situations where (for example) two Helpers with the same name exist in your Application (e.g. Plugin.HtmlHelper).
Also, this allows you override a CakePHP Helper with your own Helper. See the documentation here: Using and Configuring Helpers
Be aware that this will override the Helper everywhere in your application!
public $helpers = array(
'Html' => array(
'className' => 'AwesomeHtml'
)
);
Now, $this->Html
will actually refer to the AwesomeHtmlHelper
in your views:
echo $this->eHtml->tableHeaders(array('Date', 'Title', 'Active'));
Will output your 'enhanced' table headers
回答2:
EDIT: There was actually no error on the
return $this->Html->useTag('hiddenblock', $out);
line, it was rather a team member modification to the HtmlHelper
I didn't know about that prevented _Token
inputs to be printed in the page. The main issue remains, and it is the absence of data[_Token][key]
input in the form, which I had to add
I finally found out what was the problem. There were no _Token
fields because of a possible erorr in FormHelper
class. I had to edit the secure
method that looked like this:
public function secure($fields = array()) {
if (!isset($this->request['_Token']) ||
empty($this->request['_Token'])) {
return;
}
$locked = array();
$unlockedFields = $this->_unlockedFields;
foreach ($fields as $key => $value) {
if (!is_int($key)) {
$locked[$key] = $value;
unset($fields[$key]);
}
}
sort($unlockedFields, SORT_STRING);
sort($fields, SORT_STRING);
ksort($locked, SORT_STRING);
$fields += $locked;
$locked = implode(array_keys($locked), '|');
$unlocked = implode($unlockedFields, '|');
$fields = Security::hash(serialize($fields) . $unlocked . Configure::read('Security.salt'), 'sha1');
$out = $this->hidden('_Token.fields', array(
'value' => urlencode($fields . ':' . $locked),
'id' => 'TokenFields' . mt_rand()
));
$out .= $this->hidden('_Token.unlocked', array(
'value' => urlencode($unlocked),
'id' => 'TokenUnlocked' . mt_rand()
));
return $this->Html->useTag('hiddenblock', $out);
}
This method returned an empty string. So instead of returning the result of useTag
I simply returned $out
.
One more thing I had to do is add the data[_Token][key]
field. In the end, the method looks like this:
public function secure($fields = array()) {
if (!isset($this->request['_Token']) ||
empty($this->request['_Token'])) {
return;
}
$locked = array();
$unlockedFields = $this->_unlockedFields;
foreach ($fields as $key => $value) {
if (!is_int($key)) {
$locked[$key] = $value;
unset($fields[$key]);
}
}
sort($unlockedFields, SORT_STRING);
sort($fields, SORT_STRING);
ksort($locked, SORT_STRING);
$fields += $locked;
$locked = implode(array_keys($locked), '|');
$unlocked = implode($unlockedFields, '|');
$fields = Security::hash(serialize($fields) . $unlocked . Configure::read('Security.salt'), 'sha1');
$key = $this->request['_Token']['key'];
$out = $this->hidden('_Token.fields', array(
'value' => urlencode($fields . ':' . $locked),
'id' => 'TokenFields' . mt_rand()
));
$out .= $this->hidden('_Token.key', array(
'value' => $key,
'id' => 'TokenKey' . mt_rand()
));
$out .= $this->hidden('_Token.unlocked', array(
'value' => urlencode($unlocked),
'id' => 'TokenUnlocked' . mt_rand()
));
return $out;
//return $this->Html->useTag('hiddenblock', $out);
}
回答3:
It is difficult to test with the info you provided, but I would try to change the form create call into this:
echo $this->Form->create('User', array('url' => array('controller' => 'users', 'action' => 'login'), 'class' => 'form-signin'));
auth
type of error indicates a form validation error, or a controller/action mismatch error. That could happen if the page where you display your form is NOT /users/login
, as cross controller communication is blackholed when using Security component. Hope it helps
Edit: The validation error could also point to fact that you have not defined username
for Auth component, checkout cookbook
回答4:
Answering because I cannot comment. Have you tried adding a callback to the Security component and see what exactly it is generating?
Look at: http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html#handling-blackhole-callbacks
What exactly do you plan on using the Security component for? What are you trying to prevent?
Have you tried using Session tokens instead of per form tokens?
http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html#csrf-protection
public $components = array(
'Security' => array(
'csrfUseOnce' => false
)
);
回答5:
I had the same issue with an user login form both from the login.ctp and using it from another view as an element and managed to fix this by closing the form with $this->Form->end() otherwise it will not add the hidden inputs form the Token.... Hope this help.