php - El token se desautorizó después de intentar actualizarlo
symfony login (1)
Actualicé mi entorno de Symfony de 3.3 a 4.0. Después de la actualización, tengo problemas con el inicio de sesión (usuario proporcionado por la base de datos). Cuando envío el formulario de inicio de sesión, acabo de regresar al formulario de inicio de sesión sin ningún mensaje de error. Cuando uso credenciales no válidas, recibí el mensaje de error correspondiente. Aquí está el registro después de intentar iniciar sesión. El inicio de sesión con el proveedor de usuario "in_memory" está funcionando. necesitas más información?
[2017-12-06 13:57:05] security.INFO: User has been authenticated successfully. {"username":"***"} []
[2017-12-06 14:22:39] doctrine.DEBUG: "START TRANSACTION" [] []
[2017-12-06 13:57:05] security.DEBUG: Read existing security token from the session. {"key":"_security_secured_area","token_class":"Symfony//Component//Security//Core//Authentication//Token//UsernamePasswordToken"} []
[2017-12-06 13:57:05] doctrine.DEBUG: SELECT t0.username AS username_1, t0.password AS password_2, t0.email AS email_3, t0.email_new AS email_new_4, t0.first_name AS first_name_5, t0.last_name AS last_name_6, t0.is_active AS is_active_7, t0.email_confirmed AS email_confirmed_8, t0.shibboleth_state AS shibboleth_state_9, t0.shibboleth_hash AS shibboleth_hash_10, t0.shibboleth_persistent_id AS shibboleth_persistent_id_11, t0.confirmation_email_send AS confirmation_email_send_12, t0.last_login AS last_login_13, t0.expires AS expires_14, t0.session_id AS session_id_15, t0.id AS id_16, t0.hidden AS hidden_17, t0.deleted AS deleted_18, t0.created AS created_19, t0.modified AS modified_20, t0.sorting AS sorting_21, t0.salutation_id AS salutation_id_22, t0.creator_id AS creator_id_23, t0.modifier_id AS modifier_id_24 FROM User t0 WHERE t0.id = ? AND ((t0.deleted = 0)) [2] []
[2017-12-06 13:57:05] security.DEBUG: Token was deauthenticated after trying to refresh it. {"username":"user","provider":"Symfony//Component//Security//Core//User//ChainUserProvider"} []
[2017-12-06 13:57:05] security.INFO: Populated the TokenStorage with an anonymous Token. [] []
[2017-12-06 13:57:05] security.DEBUG: Access denied, the user is not fully authenticated; redirecting to authentication entry point. {"exception":"[object] (Symfony//Component//Security//Core//Exception//AccessDeniedException(code: 403): Access Denied. at /vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/AccessListener.php:68)"} []
[2017-12-06 13:57:05] security.DEBUG: Calling Authentication entry point. [] []
Entidad / Usuario:
class User extends EntitySuperclass implements AdvancedUserInterface, /Serializable
{
/**
* @ORM/Column(type="string")
*/
private $username;
/**
*
* @Assert/Length(max=4096,groups={"account_complete","account_password","user"})
* @Assert/Length(min = 8,groups={"account_complete","account_password","user"}, minMessage="user.password_length")
*/
private $plainPassword;
/**
* The below length depends on the "algorithm" you use for encoding
* the password, but this works well with bcrypt.
*
* @ORM/Column(type="string", length=64)
*/
private $password;
/**
* @ORM/Column(type="string", length=255)
* @Assert/NotBlank(groups={"account_register","user"})
* @Assert/Email(
* groups = {"account_register", "account","user"},
* strict = true,
* checkMX = true
* )
*/
private $email;
/**
* @ORM/Column(type="string", length=255)
*/
private $emailNew = '''';
/**
* @ORM/ManyToOne(targetEntity="Salutation")
*
*/
private $salutation;
/**
* @ORM/Column(type="string")
* @Assert/NotBlank(groups={"account_complete","user"})
* @Assert/Regex(pattern = "/^[a-zA-ZäöüÄÖÜß0-9 ]+$/",groups={"account_complete","user"}, message="user.first_name.regex")
*/
private $firstName;
/**
* @ORM/Column(type="string")
* @Assert/NotBlank(groups={"account_complete","user"})
* @Assert/Regex(pattern = "/^[a-zA-ZäöüÄÖÜß0-9 ]+$/",groups={"account_complete","user"}, message="user.last_name.regex")
*/
private $lastName;
/**
* @ORM/Column(name="is_active", type="boolean")
*/
private $isActive = false;
/**
* @ORM/Column(name="email_confirmed", type="boolean")
*/
private $emailConfirmed = false;
/**
* @ORM/Column(type="integer")
*/
private $shibbolethState = 0;
/**
* @ORM/Column(type="string")
*/
private $shibbolethHash = '''';
/**
* @ORM/Column(type="string")
*/
private $shibbolethPersistentId = '''';
/**
* @ORM/ManyToMany(targetEntity="UserGroup")
* @ORM/JoinTable(name="User_UserGroup",
* joinColumns={@ORM/JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM/JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
private $userGroups;
/**
* @ORM/Column(type="integer")
*/
private $confirmationEmailSend;
/**
* @ORM/Column(type="integer")
*/
private $lastLogin = 0;
/**
* @ORM/Column(type="integer")
*/
protected $expires = 0;
/**
* @ORM/Column(type="string", length=255)
*/
private $sessionId = '''';
/**
* @ORM/ManyToMany(targetEntity="BankDetails", cascade={"persist"})
* @ORM/JoinTable(name="User_BankDetails",
* joinColumns={@ORM/JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM/JoinColumn(name="bank_details_id", referencedColumnName="id")}
* )
* @Assert/Valid
*/
private $bankDetails;
/**
* @ORM/ManyToMany(targetEntity="Address", cascade={"persist"})
* @ORM/JoinTable(name="User_BillingAddress",
* joinColumns={@ORM/JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM/JoinColumn(name="billing_address_id", referencedColumnName="id")}
* )
* @Assert/Count(
* min = 1,
* minMessage = "user.billing_addresses.min",
* )
* @Assert/Valid
*/
private $billingAddresses;
public function __construct()
{
parent::__construct();
$this->isActive = true;
$this->confirmationEmailSend = 0;
$this->userGroups = new ArrayCollection();
$this->bankDetails = new ArrayCollection();
$this->billingAddresses = new ArrayCollection();
// may not be needed, see section on salt below
// $this->salt = md5(uniqid(null, true));
}
/**
* @ORM/PrePersist
*/
public function prePersist()
{
$currentTimestamp = time();
if($this->getConfirmationEmailSend() == NULL)
$this->setConfirmationEmailSend(0);
}
public function getUsername()
{
//return $this->username;
return $this->email;
}
public function getSalt()
{
// The bcrypt algorithm doesn''t require a separate salt.
return null;
}
public function getPassword()
{
return $this->password;
}
public function getRoles()
{
$roles = array();
$userGroups = $this->getUserGroups();
if(!empty($userGroups)) {
foreach($userGroups as $userGroup) {
$role = $userGroup->getRole();
$roles[] = ''ROLE_''.strtoupper($role);
}
}
return $roles;
}
public function isGranted($role)
{
return in_array($role, $this->getRoles());
}
public function eraseCredentials()
{
}
public function isAccountNonExpired()
{
return true;
}
public function isAccountNonLocked()
{
return true;
}
public function isCredentialsNonExpired()
{
return true;
}
public function isEnabled()
{
return $this->isActive;
}
/** @see /Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password,
$this->isActive,
// see section on salt below
// $this->salt,
));
}
/** @see /Serializable::unserialize() */
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
$this->isActive,
// see section on salt below
// $this->salt
) = unserialize($serialized);
}
/**
* Set username
*
* @param string $username
*
* @return User
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
public function getPlainPassword()
{
return $this->plainPassword;
}
public function setPlainPassword($password)
{
$this->plainPassword = $password;
}
/**
* Set password
*
* @param string $password
*
* @return User
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Set email
*
* @param string $email
*
* @return User
*/
public function setEmail($email)
{
$this->email = $email;
$this->setUsername($email);
return $this;
}
/**
* Get email
*
* @return string
*/
public function getEmail()
{
return $this->email;
}
/**
* 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;
}
/**
* Add userGroup
*
* @param /AppBundle/Entity/UserGroup $userGroup
*
* @return User
*/
public function addUserGroup(/AppBundle/Entity/UserGroup $userGroup)
{
$this->userGroups[] = $userGroup;
return $this;
}
/**
* Remove userGroup
*
* @param /AppBundle/Entity/UserGroup $userGroup
*/
public function removeUserGroup(/AppBundle/Entity/UserGroup $userGroup)
{
$this->userGroups->removeElement($userGroup);
}
/**
* Get userGroups
*
* @return /Doctrine/Common/Collections/Collection
*/
public function getUserGroups()
{
return $this->userGroups;
}
/**
* Set shibbolethPersistentId
*
* @param string $shibbolethPersistentId
*
* @return User
*/
public function setShibbolethPersistentId($shibbolethPersistentId)
{
$this->shibbolethPersistentId = $shibbolethPersistentId;
return $this;
}
/**
* Get shibbolethPersistentId
*
* @return string
*/
public function getShibbolethPersistentId()
{
return $this->shibbolethPersistentId;
}
/**
* Set firstName
*
* @param string $firstName
*
* @return User
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get firstName
*
* @return string
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* Set lastName
*
* @param string $lastName
*
* @return User
*/
public function setLastName($lastName)
{
$this->lastName = $lastName;
return $this;
}
/**
* Get lastName
*
* @return string
*/
public function getLastName()
{
return $this->lastName;
}
/**
* Set emailConfirmed
*
* @param boolean $emailConfirmed
*
* @return User
*/
public function setEmailConfirmed($emailConfirmed)
{
$this->emailConfirmed = $emailConfirmed;
return $this;
}
/**
* Get emailConfirmed
*
* @return boolean
*/
public function getEmailConfirmed()
{
return $this->emailConfirmed;
}
public function removeAllUserGroups() {
$userGroups = $this->getUserGroups();
foreach($userGroups as $userGroup) {
$this->removeUserGroup($userGroup);
}
}
public function hasUserGroup($userGroupId) {
foreach($this->getUserGroups() as $userGroup) {
if($userGroup->getId() == $userGroupId)
return true;
}
return false;
}
/**
* Set lastLogin
*
* @param integer $lastLogin
*
* @return User
*/
public function setLastLogin($lastLogin)
{
$this->lastLogin = $lastLogin;
return $this;
}
/**
* Get lastLogin
*
* @return integer
*/
public function getLastLogin()
{
return $this->lastLogin;
}
/**
* Set confirmationEmailSend
*
* @param integer $confirmationEmailSend
*
* @return User
*/
public function setConfirmationEmailSend($confirmationEmailSend)
{
$this->confirmationEmailSend = $confirmationEmailSend;
return $this;
}
/**
* Get confirmationEmailSend
*
* @return integer
*/
public function getConfirmationEmailSend()
{
return $this->confirmationEmailSend;
}
/**
* Set validTill
*
* @param integer $validTill
*
* @return User
*/
public function setValidTill($validTill)
{
$this->validTill = $validTill;
return $this;
}
/**
* Get validTill
*
* @return integer
*/
public function getValidTill()
{
return $this->validTill;
}
/**
* Set shibbolethValid
*
* @param integer $shibbolethValid
*
* @return User
*/
public function setShibbolethValid($shibbolethValid)
{
$this->shibbolethValid = $shibbolethValid;
return $this;
}
/**
* Get shibbolethValid
*
* @return integer
*/
public function getShibbolethValid()
{
return $this->shibbolethValid;
}
/**
* Set shibbolethHash
*
* @param string $shibbolethHash
*
* @return User
*/
public function setShibbolethHash($shibbolethHash)
{
$this->shibbolethHash = $shibbolethHash;
return $this;
}
/**
* Get shibbolethHash
*
* @return string
*/
public function getShibbolethHash()
{
return $this->shibbolethHash;
}
/**
* Set shibbolethState
*
* @param integer $shibbolethState
*
* @return User
*/
public function setShibbolethState($shibbolethState)
{
$this->shibbolethState = $shibbolethState;
return $this;
}
/**
* Get shibbolethState
*
* @return integer
*/
public function getShibbolethState()
{
return $this->shibbolethState;
}
/**
* Set expires
*
* @param integer $expires
*
* @return User
*/
public function setExpires($expires)
{
$this->expires = $expires;
return $this;
}
/**
* Get expires
*
* @return integer
*/
public function getExpires()
{
return $this->expires;
}
/**
* Set emailNew
*
* @param string $emailNew
*
* @return User
*/
public function setEmailNew($emailNew)
{
$this->emailNew = $emailNew;
return $this;
}
/**
* Get emailNew
*
* @return string
*/
public function getEmailNew()
{
return $this->emailNew;
}
/**
* Set passwordHash
*
* @param string $passwordHash
*
* @return User
*/
public function setPasswordHash($passwordHash)
{
$this->passwordHash = $passwordHash;
return $this;
}
/**
* Get passwordHash
*
* @return string
*/
public function getPasswordHash()
{
return $this->passwordHash;
}
/**
* Set sessionId
*
* @param string $sessionId
*
* @return User
*/
public function setSessionId($sessionId)
{
$this->sessionId = $sessionId;
return $this;
}
/**
* Get sessionId
*
* @return string
*/
public function getSessionId()
{
return $this->sessionId;
}
/**
* Set salutation
*
* @param /AppBundle/Entity/Salutation $salutation
*
* @return User
*/
public function setSalutation(/AppBundle/Entity/Salutation $salutation = null)
{
$this->salutation = $salutation;
return $this;
}
/**
* Get salutation
*
* @return /AppBundle/Entity/Salutation
*/
public function getSalutation()
{
return $this->salutation;
}
/**
* Add bankDetail
*
* @param /AppBundle/Entity/BankDetails $bankDetail
*
* @return User
*/
public function addBankDetail(/AppBundle/Entity/BankDetails $bankDetail)
{
$this->bankDetails[] = $bankDetail;
return $this;
}
/**
* Remove bankDetail
*
* @param /AppBundle/Entity/BankDetails $bankDetail
*/
public function removeBankDetail(/AppBundle/Entity/BankDetails $bankDetail)
{
$this->bankDetails->removeElement($bankDetail);
}
/**
* Get bankDetails
*
* @return /Doctrine/Common/Collections/Collection
*/
public function getBankDetails()
{
return $this->bankDetails;
}
/**
* Add billingAddress
*
* @param /AppBundle/Entity/Address $billingAddress
*
* @return User
*/
public function addBillingAddress(/AppBundle/Entity/Address $billingAddress)
{
$this->billingAddresses[] = $billingAddress;
return $this;
}
/**
* Remove billingAddress
*
* @param /AppBundle/Entity/Address $billingAddress
*/
public function removeBillingAddress(/AppBundle/Entity/Address $billingAddress)
{
$this->billingAddresses->removeElement($billingAddress);
}
/**
* Set billingAddresses
*
* @param /AppBundle/Entity/Address $billingAddress
*
* @return User
*
*/
public function setBillingAddresses(/AppBundle/Entity/Address $billingAddress)
{
if($this->billingAddresses !== NULL and $this->billingAddresses->contains($billingAddress)){
return false;
}
$this->addBillingAddress($billingAddress);
return $this;
}
/**
* Set one billingAddresses
*
* @param /AppBundle/Entity/Address $billingAddress
*
* @return User
*
*/
public function setOneBillingAddresses(/AppBundle/Entity/Address $billingAddress)
{
$this->billingAddresses = $billingAddress;
return $this;
}
/**
* Set one billingAddresses
*
* @param /AppBundle/Entity/Address $billingAddress
*
* @return User
*
*/
public function unsetBillingAddresses()
{
$this->billingAddresses = new ArrayCollection();
return $this;
}
/**
* Get billingAddresses
*
* @return /Doctrine/Common/Collections/Collection
*/
public function getBillingAddresses()
{
return $this->billingAddresses;
}
}
config / security.yml
providers:
chain_provider:
chain:
providers: [in_memory, database_user]
in_memory:
memory:
users:
admin:
password: ***
roles: ''ROLE_ADMIN''
database_user:
entity:
class: AppBundle:User
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
# pattern: match to pages
anonymous: ~
pattern: ^/
access_denied_handler: AppBundle/Security/AccessDeniedHandler
provider: chain_provider
form_login:
login_path: /login
check_path: /login_check
default_target_path: account
# Configuring CSRF protection
csrf_parameter: _csrf_security_token
csrf_token_id: a_private_string
success_handler: AppBundle/Handler/LoginSuccessHandler
logout:
path: /logout
target: /login
access_control:
...
role_hierarchy:
...
encoders:
AppBundle/Entity/User:
algorithm: bcrypt
Symfony/Component/Security/Core/User/User:
plaintext
A partir de Symfony 4.0,
logout_on_user_change
está establecido en
true
.
Eso significa que un usuario se cerrará sesión si se ha cambiado.
Debe implementar
Symfony/Component/Security/Core/User/EquatableInterface
y agregar el método
isEqualTo
:
class User implements EquatableInterface
{
public function isEqualTo(UserInterface $user)
{
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->salt !== $user->getSalt()) {
return false;
}
if ($this->username !== $user->getUsername()) {
return false;
}
return true;
}
}
Registro de cambios
https://github.com/symfony/security-bundle/blob/master/CHANGELOG.md
4.1.0
La opción de firewall
logout_on_user_change
está en desuso y se eliminará en 5.0.4.0.0
la opción de cortafuegos
logout_on_user_change
ahora es siempre verdadera, lo que activará un cierre de sesión si el usuario cambia entre solicitudes3.4.0
Se agregó
logout_on_user_change
a las opciones del firewall. Este elemento de configuración activará un cierre de sesión cuando el usuario haya cambiado. Debe establecerse en verdadero para evitar desaprobaciones en la configuración.
La opción no estaba documentada al momento de escribir esta respuesta: https://github.com/symfony/symfony-docs/issues/8428 , pero ahora es: https://symfony.com/doc/current/reference/configuration/security.html#logout-on-user-change
Nota al margen sobre la actualización a una nueva versión principal
Si desea actualizar a una nueva versión principal, siempre actualice primero a la última versión secundaria. Eso significa actualizar a 2.8 antes de actualizar a 3.0 y actualizar a 3.4 antes de pasar a 4.0. Consulte Symfony 4: Componga sus aplicaciones por Fabien Potencier.
Symfony 3.0 = Symfony 2.8 - funciones obsoletas
(..)
Symfony 4.0 = Symfony 3.4 - características obsoletas + una nueva forma de desarrollar aplicaciones
La actualización a una nueva versión principal es mucho más fácil si ya está en la última versión menor, porque puede ver todos los avisos de desaprobación.