前奏
- Symfony 对每个请求的用户进行身份验证,因此每次刷新视图时它都会从用户实体调用
getPassword() 方法。
- Symfony 仅在
'_security_main' 会话密钥中的序列化令牌PostAuthenticationGuardToken 中存储经过身份验证(登录)的用户数据(也是密码哈希),以及容器内的每个(普通或匿名)经过身份验证的用户令牌。要查看特定令牌中的数据,请使用:
-
dump(\unserialize($request->getSession()->get('_security_main'))); 登录用户
-
dump($this->container->get('security.token_storage')->getToken()); 每个用户
- 当您更改数据库中的密码并同时登录时,
PostAuthenticationGuardToken 用户令牌更改为AnonymousToken,因此is_granted('ROLE_ADMIN') 失败。
幕后
查看命名空间Symfony\Component\Security\Core\Authentication\Token:https://github.com/symfony/security-core/tree/4.2/Authentication/Token。
点击它,让下面的信息变得简单 150%。
AbstractToken 类是 PostAuthenticationGuardToken 和 AnonymousToken 的基础。 AbstractToken 有 hasUserChanged(UserInterface $user) 方法,用于确定用户敏感数据是否已更改。
在这个类内部是另一个方法setUser($user),它确定是否应该对用户进行身份验证(也基于hasUserChanged),它为扩展这个类的令牌类设置用户实体。
AbstractToken.php
if ($changed) {
$this->setAuthenticated(false);
}
更改密码后未验证的用户?!是的,但仅适用于抽象类。
普通用户和匿名用户都经过身份验证。很简单:
AnonymousToken.php
class AnonymousToken extends AbstractToken
{
private $secret;
public function __construct(string $secret, $user, array $roles = [])
{
parent::__construct($roles);
$this->secret = $secret;
$this->setUser($user);
$this->setAuthenticated(true); // here you go :-)
}
// (...)
现在是真正的if 导致所有问题......
AbstractToken.php
if ($this->user->getPassword() !== $user->getPassword()) {
return true;
}
将存储在会话中的用户与您的提供者的 refreshUser 返回的用户进行比较。
Symfony & Guard: "The security token was removed due to an AccountStatusException"
解决方案
感谢:Alain Tiemblo
在你的用户实体中实现EquatableInterface
用户.php
use Symfony\Component\Security\Core\User\{UserInterface, EquatableInterface};
class User implements UserInterface, EquatableInterface
{
// (...)
}
从此接口覆盖isEqualTo方法
用户.php
public function isEqualTo(UserInterface $user)
{
return $user->getId() === $this->getId();
}
完成。现在每次在数据库中更改密码时调用hasUserChanged,不会执行密码检查if。
证明:
AbstractToken.php
private function hasUserChanged(UserInterface $user)
{
if (!($this->user instanceof UserInterface)) {
throw new \BadMethodCallException('Method "hasUserChanged" should be called when current user class is instance of "UserInterface".');
}
if ($this->user instanceof EquatableInterface) {
return !(bool) $this->user->isEqualTo($user);
// THIS is executed and FALSE is returned because user instances have the same ids but different passwords :)
}
if ($this->user->getPassword() !== $user->getPassword()) {
return true;
}
// other checks like salt or username below
现在您可以回到前奏 2.1 和 2.2 并检查结果。