I updated my Symfony environment form 3.3 to 4.0. After the update I have problems with the login (user provided by database). When I submit the login form, I just got right back to the login form without any error message. When I use invalid credentials, I got the corresponding error message. Here is the log after trying to login. The login with the "in_memory" user provider is working. Do you need more information?
[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. [] []
Entity\User:
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
As of Symfony 4.0, logout_on_user_change
is set to true
. That means a user will be logged out if it has been changed.
You should implement Symfony\Component\Security\Core\User\EquatableInterface
and add the isEqualTo
method:
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;
}
}
https://github.com/symfony/security-bundle/blob/master/CHANGELOG.md
4.1.0
The
logout_on_user_change
firewall option is deprecated and will be removed in 5.0.4.0.0
the firewall option
logout_on_user_change
is now always true, which will trigger a logout if the user changes between requests3.4.0
Added
logout_on_user_change
to the firewall options. This config item will trigger a logout when the user has changed. Should be set to true to avoid deprecations in the configuration.
The option wasn't documented by the time of writing this answer: https://github.com/symfony/symfony-docs/issues/8428, but it now is: https://symfony.com/doc/4.4/reference/configuration/security.html#logout-on-user-change
If you want to upgrade to a new major version, always update to the latest minor version first. That means update to 2.8 before updating to 3.0 and updating to 3.4 before going to 4.0. See Symfony 4: Compose your Applications by Fabien Potencier.
Symfony 3.0 = Symfony 2.8 - deprecated features
(..)
Symfony 4.0 = Symfony 3.4 - deprecated features + a new way to develop applications
Updating to a new major release is much easier if you're already on the latest minor release, because you can see all deprecation notices.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With