我正在研究Symfony 2,并试图创建我的身份验证用户。我遵循文档,但如果我实现AdvancedUserInterface
,我的用户没有经过身份验证,相反,如果我使用UserInterface
,一切都很好。
我的代码是:
控制器:
public function saveAction(Request $request) {
$user = new User();
$form = $this->createForm(new UserType(), $user);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$encodeFactory = $this->container->get('security.encoder_factory');
$encoder = $encodeFactory->getEncoder($user);
$user->setPassword($encoder->encodePassword($request->get($form->getName())['password'], $user->getSalt()));
$em->persist($user);
$em->flush();
return $this->redirect($this->generateUrl('index'));
}
return array(
'form' => $form
);
实体:
<?php
namespace FOO'UserBundle'Entity;
use Doctrine'ORM'Mapping as ORM;
use Symfony'Component'Security'Core'Role'Role;
use Symfony'Component'Security'Core'User'AdvancedUserInterface;
/**
* User
*
* @ORM'Table()
* @ORM'Entity(repositoryClass="FOO'UserBundle'Entity'UserRepository")
*/
class User implements AdvancedUserInterface, 'Serializable {
/**
* @var integer
*
* @ORM'Column(name="id", type="integer")
* @ORM'Id
* @ORM'GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM'Column(name="username", type="string", length=255)
*/
private $username;
/**
* @var string
*
* @ORM'Column(name="salt", type="string", length=255)
*/
private $salt;
/**
* @var string
*
* @ORM'Column(name="password", type="string", length=255)
*/
private $password;
/**
* @var boolean
*
* @ORM'Column(name="is_active", type="boolean")
*/
private $isActive;
public function __construct(){
$this->isActive = true;
$this->salt= md5(uniqid(null, true));
}
/**
* Get id
*
* @return integer
*/
public function getId() {
return $this->id;
}
/**
* Set username
*
* @param string $username
* @return User
*/
public function setUsername($username) {
$this->username = $username;
return $this;
}
/**
* Get username
*
* @return string
*/
public function getUsername() {
return $this->username;
}
/**
* Set salt
*
* @param string $salt
* @return User
*/
public function setSalt($salt) {
$this->salt = $salt;
return $this;
}
/**
* Get salt
*
* @return string
*/
public function getSalt() {
return $this->salt;
}
/**
* Set password
*
* @param string $password
* @return User
*/
public function setPassword($password) {
$this->password = $password;
return $this;
}
/**
* Get password
*
* @return string
*/
public function getPassword() {
return $this->password;
}
/**
* Set isActive
*
* @param boolean $isActive
* @return User
*/
public function setIsActive($isActive)
{
$this->isActive = $isActive;
return $this;
}
/**
* Get isActive
*
* @return boolean
*/
public function getIsActive()
{
return $this->isActive;
}
/**
* Checks whether the user's account has expired.
*
* Internally, if this method returns false, the authentication system
* will throw an AccountExpiredException and prevent login.
*
* @return bool true if the user's account is non expired, false otherwise
*
* @see AccountExpiredException
*/
public function isAccountNonExpired() {
return true;
}
/**
* Checks whether the user is locked.
*
* Internally, if this method returns false, the authentication system
* will throw a LockedException and prevent login.
*
* @return bool true if the user is not locked, false otherwise
*
* @see LockedException
*/
public function isAccountNonLocked() {
return true;
}
/**
* Checks whether the user's credentials (password) has expired.
*
* Internally, if this method returns false, the authentication system
* will throw a CredentialsExpiredException and prevent login.
*
* @return bool true if the user's credentials are non expired, false otherwise
*
* @see CredentialsExpiredException
*/
public function isCredentialsNonExpired() {
return true;
}
/**
* Checks whether the user is enabled.
*
* Internally, if this method returns false, the authentication system
* will throw a DisabledException and prevent login.
*
* @return bool true if the user is enabled, false otherwise
*
* @see DisabledException
*/
public function isEnabled() {
return $this->isActive;
}
/**
* (PHP 5 >= 5.1.0)<br/>
* String representation of object
* @link http://php.net/manual/en/serializable.serialize.php
* @return string the string representation of the object or null
*/
public function serialize() {
return serialize(array(
$this->id,
$this->username,
$this->password,
$this->salt,
));
}
/**
* (PHP 5 >= 5.1.0)<br/>
* Constructs the object
* @link http://php.net/manual/en/serializable.unserialize.php
* @param string $serialized <p>
* The string representation of the object.
* </p>
* @return void
*/
public function unserialize($serialized) {
list (
$this->id,
$this->username,
$this->password,
$this->salt
) = unserialize($serialized);
}
/**
* Returns the roles granted to the user.
*
* <code>
* public function getRoles()
* {
* return array('ROLE_USER');
* }
* </code>
*
* Alternatively, the roles might be stored on a ``roles`` property,
* and populated in any number of different ways when the user object
* is created.
*
* @return Role[] The user roles
*/
public function getRoles() {
return array('ROLE_USER');
}
/**
* Removes sensitive data from the user.
*
* This is important if, at any given point, sensitive information like
* the plain-text password is stored on this object.
*/
public function eraseCredentials() {
}
}
security.yml
security:
encoders:
Symfony'Component'Security'Core'User'User: plaintext
FOO'UserBundle'Entity'User:
algorithm: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
administrators:
entity:
class: FOOUserBundle:User
property: username
in_memory:
memory:
users:
user: { password: userpass, roles: [ 'ROLE_USER' ] }
admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
FOO:
pattern: ^/
anonymous: ~
form_login:
login_path: login
check_path: login_check
csrf_provider: form.csrf_provider
logout:
path: /logout
target: /
remember_me:
key: "%secret%"
lifetime: 31536000 # 365 giorni in secondi
path: /
domain: ~ # Defaults to the current domain from $_SERVER
#anonymous: ~
#http_basic:
# realm: "Secured Demo Area"
access_control:
- { path: ^/demo/secured/hello/admin/, roles: ROLE_ADMIN }
#- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
在取消序列化时,不会调用类的构造函数。您可以通过创建一个新的Object并像这样序列化和取消序列化来轻松地检查这一点:
$user = new 'FOO'UserBundle'EntityUser();
var_dump($user);
$serialized = serialize($user);
var_dump($serialized);
$uUser = unserialize($serialized);
var_dump($uUser);
/**
It output something like this:
class FOO'UserBundle'Entity'User#1 (5) {
private $id =>
NULL
private $username =>
NULL
private $salt =>
string(32) "296cb7bebc4aff07a3cc5bb0da746707"
private $password =>
NULL
private $isActive =>
bool(true)
}
string(107) "C:26:"FOO'UserBundle'Entity'User":68:{a:4:{i:0;N;i:1;N;i:2;N;i:3;s:32:"296cb7bebc4aff07a3cc5bb0da746707";}}"
class FOO'UserBundle'Entity'User#2 (5) {
private $id =>
NULL
private $username =>
NULL
private $salt =>
string(32) "296cb7bebc4aff07a3cc5bb0da746707"
private $password =>
NULL
private $isActive =>
NULL
}
*/
因此,您必须将isActive添加到serialize/unserialize方法中,或者在unserialize方法中手动调用构造函数。
这种行为背后的真正问题,可以在AbstractToken类中找到:Symfony在配置的用户提供程序发出的每个请求时都会重新加载用户,并将其设置为Token实例(从AbstractToken继承setUser方法)。在这个方法Symfony中,如果用户更改了,请对照未序列化的数据进行检查。如果您的用户实现AdvancedUserInterface,则会有其他测试:isAccountNonExpired、isAccountNonLocked、isCredentialsNonExpiredAND isEnabled。因此,从数据库加载的用户对于isEnabled返回true,未序列化的用户返回NULL,这是不相等的。根据这个事实,AbstractToken认为您的用户已经更改,并将令牌设置为authenticated=false。
这里有一些来自AbstractToken的代码:
# file vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php
<?php
// [...]
namespace Symfony'Component'Security'Core'Authentication'Token;
// [...]
abstract class AbstractToken implements TokenInterface
{
// [...]
public function setUser($user)
{
if (!($user instanceof UserInterface || (is_object($user) && method_exists($user, '__toString')) || is_string($user))) {
throw new 'InvalidArgumentException('$user must be an instanceof UserInterface, an object implementing a __toString method, or a primitive string.');
}
if (null === $this->user) {
$changed = false;
} elseif ($this->user instanceof UserInterface) {
if (!$user instanceof UserInterface) {
$changed = true;
} else {
$changed = $this->hasUserChanged($user);
}
} elseif ($user instanceof UserInterface) {
$changed = true;
} else {
$changed = (string) $this->user !== (string) $user;
}
if ($changed) {
// From here the user is not authenticated any more
$this->setAuthenticated(false);
}
$this->user = $user;
}
// [...]
private function hasUserChanged(UserInterface $user)
{
// [...] Some other checks wich are ok
if ($this->user instanceof AdvancedUserInterface && $user instanceof AdvancedUserInterface) {
// [...]
// Here is the problem with the unserialized user
if ($this->user->isEnabled() !== $user->isEnabled()) {
return true;
}
// [...]
}
// [...]
}