Symfony authenticator:在控制器中,尽管设置了一个值,但条令返回带有空字符串字段的用户项

Symfony authenticator:在控制器中,尽管设置了一个值,但条令返回带有空字符串字段的用户项,symfony,doctrine-orm,doctrine,symfony-3.4,doctrine-query,Symfony,Doctrine Orm,Doctrine,Symfony 3.4,Doctrine Query,我的数据库模式主要由以下实体组成:用户和身份验证令牌。每个用户可以有多个身份验证令牌 问题是:在控制器中选择当前经过身份验证的用户时,字符串字段saltedPasswordHash为空(“”),尽管数据库中设置了一个值。在ApiKeyAuthenticator.php中获取saltedPasswordHash是可行的(请查看两条TODO注释) 无论出于何种原因,选择email(字符串)或created(日期时间)字段都有效。使用saltedPasswordHash持久化新用户实体或选择任何其他用

我的数据库模式主要由以下实体组成:用户和身份验证令牌。每个用户可以有多个身份验证令牌

问题是:在控制器中选择当前经过身份验证的用户时,字符串字段saltedPasswordHash为空(“”),尽管数据库中设置了一个值。在ApiKeyAuthenticator.php中获取saltedPasswordHash是可行的(请查看两条TODO注释)

无论出于何种原因,选择email(字符串)或created(日期时间)字段都有效。使用saltedPasswordHash持久化新用户实体或选择任何其他用户项都可以

APIKeyAuthenticator正在处理授权。禁用防火墙和身份验证时,一切正常。我在下面包括了源文件

我正在使用PHP7.2.15-1和mysql版本15.1发行版10.3.13-MariaDB

Security/ApiKeyAuthenticator.php

namespace App\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;

class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
    public function createToken(Request $request, $providerKey)
    {
        $apiKey = $request->headers->get('authToken');

        if (!$apiKey) {
            throw new BadCredentialsException();

            // or to just skip api key authentication
            // return null;
        }

        return new PreAuthenticatedToken(
            'anon.',
            $apiKey,
            $providerKey
        );
    }

    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
    }

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        if (!$userProvider instanceof ApiKeyUserProvider) {
            throw new \InvalidArgumentException(
                sprintf(
                    'The user provider must be an instance of ApiKeyUserProvider (%s was given).',
                    get_class($userProvider)
                )
            );
        }

        $apiKey = $token->getCredentials();
        $username = $userProvider->getUsernameForApiKey($apiKey);

        if (!$username) {
            // CAUTION: this message will be returned to the client
            // (so don't put any un-trusted messages / error strings here)
            throw new BadCredentialsException(
                sprintf('API Key "%s" does not exist.', $apiKey)
            );
        }

        $user = $userProvider->loadUserByAuthToken($apiKey);

        if (!isset($user)) {
            throw new BadCredentialsException(
                sprintf('API Key "%s" does not exist.', $apiKey)
            );
        }

        // TODO: HERE, THE $user->getSaltedPasswordHash() RETURNS THE CORRECT VALUE!

        return new PreAuthenticatedToken(
            $user, // TODO: with "new User()" instead, it works!
            $apiKey,
            $providerKey,
            $user->getRoles()
        );
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        return new Response(
        // this contains information about *why* authentication failed
        // use it, or return your own message
            strtr($exception->getMessageKey(), $exception->getMessageData()),
            401
        );
    }
}
namespace App\Security;

use App\Entity\AuthToken;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class ApiKeyUserProvider implements UserProviderInterface
{
    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * ApiKeyUserProvider constructor.
     * @param EntityManagerInterface $em
     */
    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

    public function getUsernameForApiKey($apiKey)
    {
        return $apiKey;
    }

    public function loadUserByUsername($username)
    {
        // TODO: Implement loadUserByUsername() method.
    }

    /**
     * Auth token is used as username
     *
     * @param string $authToken
     * @return null|UserInterface
     */
    public function loadUserByAuthToken($authToken): ?UserInterface
    {
        if (!isset($authToken)) {
            return null;
        }

        $token = $this->em
            ->getRepository(AuthToken::class)
            ->findOneBy(['id' => AuthToken::hex2dec($authToken)]);

        if (!isset($token)) {
            return null;
        }

        return $token->getUser();
    }

    public
    function refreshUser(UserInterface $user)
    {
        // this is used for storing authentication in the session
        // but in this example, the token is sent in each request,
        // so authentication can be stateless. Throwing this exception
        // is proper to make things stateless
        throw new UnsupportedUserException();
    }

    public
    function supportsClass($class)
    {
        return User::class === $class;
    }
}
namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface, EquatableInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * This needs to be nullable because the email includes the id of newly created users, which can only be obtained after inserting the new record.
     * @ORM\Column(type="string", length=255, nullable=true, unique=true)
     * @Assert\Length(max=255)
     * @Assert\NotBlank()
     */
    private $email;

    /**
     * Set null to disable login
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\Length(max=255)
     * @Assert\NotBlank()
     */
    private $saltedPasswordHash;

    /**
     * @ORM\Column(type="datetime")
     */
    private $created;

    // ...

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\AuthToken", mappedBy="user", fetch="LAZY")
     */
    private $authTokens;

    /**
     * @ORM\Column(type="string", length=5)
     */
    private $role;

    // ...

    public function __construct()
    {
        $this->role = 'user';
        $this->saltedPasswordHash = null;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getSaltedPasswordHash(): ?string
    {
        return $this->saltedPasswordHash;
    }

    public function setSaltedPasswordHash(?string $saltedPasswordHash): self
    {
        $this->saltedPasswordHash = $saltedPasswordHash;

        return $this;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(?string $email): self
    {
        $this->email = $email;

        return $this;
    }

    public function getCreated(): ?\DateTimeInterface
    {
        return $this->created;
    }

    public function setCreated(\DateTimeInterface $created): self
    {
        $this->created = $created;

        return $this;
    }


    /**
     * @return ArrayCollection
     */
    public function getAuthTokens()
    {
        return $this->authTokens;
    }

    /**
     * @param ArrayCollection $authTokens
     * @return User
     */
    public function setAuthTokens(ArrayCollection $authTokens): User
    {
        $this->authTokens = $authTokens;

        return $this;
    }

    /**
     * @param AuthToken $authToken
     * @return User
     */
    public function addAuthToken(AuthToken $authToken): User
    {
        $this->authTokens->add($authToken);

        return $this;
    }

    /**
     * @param AuthToken $authToken
     * @return User
     */
    public function removeAuthToken(AuthToken $authToken): User
    {
        $this->authTokens->removeElement($authToken);

        return $this;
    }

    // ...

    /**
     * Returns the password used to authenticate the user.
     *
     * This should be the encoded password. On authentication, a plain-text
     * password will be salted, encoded, and then compared to this value.
     *
     * @return string The password
     */
    public function getPassword()
    {
        return $this->getSaltedPasswordHash();
    }

    /**
     * Returns the salt that was originally used to encode the password.
     *
     * This can return null if the password was not encoded using a salt.
     *
     * @return string|null The salt
     */
    public function getSalt()
    {
        // TODO: Implement getSalt() method.
    }

    /**
     * Returns the username used to authenticate the user.
     *
     * @return string The username
     */
    public function getUsername()
    {
        return $this->getEmail();
    }

    /**
     * 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()
    {
        $this->setSaltedPasswordHash('');
    }

    /**
     * @return mixed
     */
    public function getRole()
    {
        return $this->role;
    }

    /**
     * @param mixed $role
     * @return User
     */
    public function setRole($role): User
    {
        $this->role = $role;

        return $this;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return "User " . $this->email;
    }

    /**
     * The equality comparison should neither be done by referential equality
     * nor by comparing identities (i.e. getId() === getId()).
     *
     * However, you do not need to compare every attribute, but only those that
     * are relevant for assessing whether re-authentication is required.
     *
     * Also implementation should consider that $user instance may implement
     * the extended user interface `AdvancedUserInterface`.
     *
     * https://stackoverflow.com/a/39884792/6144818
     *
     * @param UserInterface $user
     * @return bool
     */
    public function isEqualTo(UserInterface $user)
    {
        return (
                $this->getUsername() == $user->getUsername()
            ) && (
                $this->getRoles() == $user->getRoles()
            );
    }

    // ...
}
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\AuthTokenRepository")
 */
class AuthToken
{
    /**
     * @ORM\Id()
     * @ORM\Column(type="decimal", precision=32, scale=0, options={"unsigned": true})
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="authTokens")
     */
    private $user;

    /**
     * @ORM\Column(type="datetime")
     */
    private $added;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $lastSeen;

    /**
     * @ORM\Column(type="string", length=12, nullable=true)
     */
    private $apiVersion;

    /**
     * @return string
     */
    public function getId(): string
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getHexId(): string
    {
        return $this->dec2hex($this->id);
    }

    /**
     * @param mixed $id
     */
    public function setId($id): void
    {
        $this->id = $id;
    }

    /**
     * @param mixed $id
     * @throws \Exception
     */
    public function generateId(): void
    {
        $length = 32;

        $str = "";
        $characters = range('0', '9');
        $max = count($characters) - 1;

        for ($i = 0; $i < $length; $i++) {
            $rand = random_int(0, $max);
            $str .= $characters[$rand];
        }

        $this->id = $str;
    }

    /**
     * @return mixed
     */
    public function getUser()
    {
        return $this->user;
    }

    /**
     * @param mixed $user
     */
    public function setUser($user): void
    {
        $this->user = $user;
    }

    /**
     * @return mixed
     */
    public function getAdded()
    {
        return $this->added;
    }

    /**
     * @param mixed $added
     */
    public function setAdded($added): void
    {
        $this->added = $added;
    }

    /**
     * @return mixed
     */
    public function getLastSeen()
    {
        return $this->lastSeen;
    }

    /**
     * @param mixed $lastSeen
     */
    public function setLastSeen($lastSeen): void
    {
        $this->lastSeen = $lastSeen;
    }

    public function getApiVersion(): ?string
    {
        return $this->apiVersion;
    }

    public function setApiVersion(string $apiVersion): self
    {
        $this->apiVersion = $apiVersion;

        return $this;
    }

    public static function dec2hex(string $dec): string
    {
        $hex = '';
        do {
            $last = bcmod($dec, 16);
            $hex = dechex($last) . $hex;
            $dec = bcdiv(bcsub($dec, $last), 16);
        } while ($dec > 0);
        return $hex;
    }

    public static function hex2dec($hex)
    {
        $dec = '0';
        $len = strlen($hex);
        for ($i = 1; $i <= $len; $i++)
            $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));

        return $dec;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return "AuthToken " . $this->id . " (" . $this->user . ")";
    }

}
Security/ApiKeyUserProvider.php

namespace App\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;

class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
    public function createToken(Request $request, $providerKey)
    {
        $apiKey = $request->headers->get('authToken');

        if (!$apiKey) {
            throw new BadCredentialsException();

            // or to just skip api key authentication
            // return null;
        }

        return new PreAuthenticatedToken(
            'anon.',
            $apiKey,
            $providerKey
        );
    }

    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
    }

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        if (!$userProvider instanceof ApiKeyUserProvider) {
            throw new \InvalidArgumentException(
                sprintf(
                    'The user provider must be an instance of ApiKeyUserProvider (%s was given).',
                    get_class($userProvider)
                )
            );
        }

        $apiKey = $token->getCredentials();
        $username = $userProvider->getUsernameForApiKey($apiKey);

        if (!$username) {
            // CAUTION: this message will be returned to the client
            // (so don't put any un-trusted messages / error strings here)
            throw new BadCredentialsException(
                sprintf('API Key "%s" does not exist.', $apiKey)
            );
        }

        $user = $userProvider->loadUserByAuthToken($apiKey);

        if (!isset($user)) {
            throw new BadCredentialsException(
                sprintf('API Key "%s" does not exist.', $apiKey)
            );
        }

        // TODO: HERE, THE $user->getSaltedPasswordHash() RETURNS THE CORRECT VALUE!

        return new PreAuthenticatedToken(
            $user, // TODO: with "new User()" instead, it works!
            $apiKey,
            $providerKey,
            $user->getRoles()
        );
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        return new Response(
        // this contains information about *why* authentication failed
        // use it, or return your own message
            strtr($exception->getMessageKey(), $exception->getMessageData()),
            401
        );
    }
}
namespace App\Security;

use App\Entity\AuthToken;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class ApiKeyUserProvider implements UserProviderInterface
{
    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * ApiKeyUserProvider constructor.
     * @param EntityManagerInterface $em
     */
    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

    public function getUsernameForApiKey($apiKey)
    {
        return $apiKey;
    }

    public function loadUserByUsername($username)
    {
        // TODO: Implement loadUserByUsername() method.
    }

    /**
     * Auth token is used as username
     *
     * @param string $authToken
     * @return null|UserInterface
     */
    public function loadUserByAuthToken($authToken): ?UserInterface
    {
        if (!isset($authToken)) {
            return null;
        }

        $token = $this->em
            ->getRepository(AuthToken::class)
            ->findOneBy(['id' => AuthToken::hex2dec($authToken)]);

        if (!isset($token)) {
            return null;
        }

        return $token->getUser();
    }

    public
    function refreshUser(UserInterface $user)
    {
        // this is used for storing authentication in the session
        // but in this example, the token is sent in each request,
        // so authentication can be stateless. Throwing this exception
        // is proper to make things stateless
        throw new UnsupportedUserException();
    }

    public
    function supportsClass($class)
    {
        return User::class === $class;
    }
}
namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface, EquatableInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * This needs to be nullable because the email includes the id of newly created users, which can only be obtained after inserting the new record.
     * @ORM\Column(type="string", length=255, nullable=true, unique=true)
     * @Assert\Length(max=255)
     * @Assert\NotBlank()
     */
    private $email;

    /**
     * Set null to disable login
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\Length(max=255)
     * @Assert\NotBlank()
     */
    private $saltedPasswordHash;

    /**
     * @ORM\Column(type="datetime")
     */
    private $created;

    // ...

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\AuthToken", mappedBy="user", fetch="LAZY")
     */
    private $authTokens;

    /**
     * @ORM\Column(type="string", length=5)
     */
    private $role;

    // ...

    public function __construct()
    {
        $this->role = 'user';
        $this->saltedPasswordHash = null;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getSaltedPasswordHash(): ?string
    {
        return $this->saltedPasswordHash;
    }

    public function setSaltedPasswordHash(?string $saltedPasswordHash): self
    {
        $this->saltedPasswordHash = $saltedPasswordHash;

        return $this;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(?string $email): self
    {
        $this->email = $email;

        return $this;
    }

    public function getCreated(): ?\DateTimeInterface
    {
        return $this->created;
    }

    public function setCreated(\DateTimeInterface $created): self
    {
        $this->created = $created;

        return $this;
    }


    /**
     * @return ArrayCollection
     */
    public function getAuthTokens()
    {
        return $this->authTokens;
    }

    /**
     * @param ArrayCollection $authTokens
     * @return User
     */
    public function setAuthTokens(ArrayCollection $authTokens): User
    {
        $this->authTokens = $authTokens;

        return $this;
    }

    /**
     * @param AuthToken $authToken
     * @return User
     */
    public function addAuthToken(AuthToken $authToken): User
    {
        $this->authTokens->add($authToken);

        return $this;
    }

    /**
     * @param AuthToken $authToken
     * @return User
     */
    public function removeAuthToken(AuthToken $authToken): User
    {
        $this->authTokens->removeElement($authToken);

        return $this;
    }

    // ...

    /**
     * Returns the password used to authenticate the user.
     *
     * This should be the encoded password. On authentication, a plain-text
     * password will be salted, encoded, and then compared to this value.
     *
     * @return string The password
     */
    public function getPassword()
    {
        return $this->getSaltedPasswordHash();
    }

    /**
     * Returns the salt that was originally used to encode the password.
     *
     * This can return null if the password was not encoded using a salt.
     *
     * @return string|null The salt
     */
    public function getSalt()
    {
        // TODO: Implement getSalt() method.
    }

    /**
     * Returns the username used to authenticate the user.
     *
     * @return string The username
     */
    public function getUsername()
    {
        return $this->getEmail();
    }

    /**
     * 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()
    {
        $this->setSaltedPasswordHash('');
    }

    /**
     * @return mixed
     */
    public function getRole()
    {
        return $this->role;
    }

    /**
     * @param mixed $role
     * @return User
     */
    public function setRole($role): User
    {
        $this->role = $role;

        return $this;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return "User " . $this->email;
    }

    /**
     * The equality comparison should neither be done by referential equality
     * nor by comparing identities (i.e. getId() === getId()).
     *
     * However, you do not need to compare every attribute, but only those that
     * are relevant for assessing whether re-authentication is required.
     *
     * Also implementation should consider that $user instance may implement
     * the extended user interface `AdvancedUserInterface`.
     *
     * https://stackoverflow.com/a/39884792/6144818
     *
     * @param UserInterface $user
     * @return bool
     */
    public function isEqualTo(UserInterface $user)
    {
        return (
                $this->getUsername() == $user->getUsername()
            ) && (
                $this->getRoles() == $user->getRoles()
            );
    }

    // ...
}
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\AuthTokenRepository")
 */
class AuthToken
{
    /**
     * @ORM\Id()
     * @ORM\Column(type="decimal", precision=32, scale=0, options={"unsigned": true})
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="authTokens")
     */
    private $user;

    /**
     * @ORM\Column(type="datetime")
     */
    private $added;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $lastSeen;

    /**
     * @ORM\Column(type="string", length=12, nullable=true)
     */
    private $apiVersion;

    /**
     * @return string
     */
    public function getId(): string
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getHexId(): string
    {
        return $this->dec2hex($this->id);
    }

    /**
     * @param mixed $id
     */
    public function setId($id): void
    {
        $this->id = $id;
    }

    /**
     * @param mixed $id
     * @throws \Exception
     */
    public function generateId(): void
    {
        $length = 32;

        $str = "";
        $characters = range('0', '9');
        $max = count($characters) - 1;

        for ($i = 0; $i < $length; $i++) {
            $rand = random_int(0, $max);
            $str .= $characters[$rand];
        }

        $this->id = $str;
    }

    /**
     * @return mixed
     */
    public function getUser()
    {
        return $this->user;
    }

    /**
     * @param mixed $user
     */
    public function setUser($user): void
    {
        $this->user = $user;
    }

    /**
     * @return mixed
     */
    public function getAdded()
    {
        return $this->added;
    }

    /**
     * @param mixed $added
     */
    public function setAdded($added): void
    {
        $this->added = $added;
    }

    /**
     * @return mixed
     */
    public function getLastSeen()
    {
        return $this->lastSeen;
    }

    /**
     * @param mixed $lastSeen
     */
    public function setLastSeen($lastSeen): void
    {
        $this->lastSeen = $lastSeen;
    }

    public function getApiVersion(): ?string
    {
        return $this->apiVersion;
    }

    public function setApiVersion(string $apiVersion): self
    {
        $this->apiVersion = $apiVersion;

        return $this;
    }

    public static function dec2hex(string $dec): string
    {
        $hex = '';
        do {
            $last = bcmod($dec, 16);
            $hex = dechex($last) . $hex;
            $dec = bcdiv(bcsub($dec, $last), 16);
        } while ($dec > 0);
        return $hex;
    }

    public static function hex2dec($hex)
    {
        $dec = '0';
        $len = strlen($hex);
        for ($i = 1; $i <= $len; $i++)
            $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));

        return $dec;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return "AuthToken " . $this->id . " (" . $this->user . ")";
    }

}
Entity/User.php

namespace App\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;

class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
    public function createToken(Request $request, $providerKey)
    {
        $apiKey = $request->headers->get('authToken');

        if (!$apiKey) {
            throw new BadCredentialsException();

            // or to just skip api key authentication
            // return null;
        }

        return new PreAuthenticatedToken(
            'anon.',
            $apiKey,
            $providerKey
        );
    }

    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
    }

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        if (!$userProvider instanceof ApiKeyUserProvider) {
            throw new \InvalidArgumentException(
                sprintf(
                    'The user provider must be an instance of ApiKeyUserProvider (%s was given).',
                    get_class($userProvider)
                )
            );
        }

        $apiKey = $token->getCredentials();
        $username = $userProvider->getUsernameForApiKey($apiKey);

        if (!$username) {
            // CAUTION: this message will be returned to the client
            // (so don't put any un-trusted messages / error strings here)
            throw new BadCredentialsException(
                sprintf('API Key "%s" does not exist.', $apiKey)
            );
        }

        $user = $userProvider->loadUserByAuthToken($apiKey);

        if (!isset($user)) {
            throw new BadCredentialsException(
                sprintf('API Key "%s" does not exist.', $apiKey)
            );
        }

        // TODO: HERE, THE $user->getSaltedPasswordHash() RETURNS THE CORRECT VALUE!

        return new PreAuthenticatedToken(
            $user, // TODO: with "new User()" instead, it works!
            $apiKey,
            $providerKey,
            $user->getRoles()
        );
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        return new Response(
        // this contains information about *why* authentication failed
        // use it, or return your own message
            strtr($exception->getMessageKey(), $exception->getMessageData()),
            401
        );
    }
}
namespace App\Security;

use App\Entity\AuthToken;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class ApiKeyUserProvider implements UserProviderInterface
{
    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * ApiKeyUserProvider constructor.
     * @param EntityManagerInterface $em
     */
    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

    public function getUsernameForApiKey($apiKey)
    {
        return $apiKey;
    }

    public function loadUserByUsername($username)
    {
        // TODO: Implement loadUserByUsername() method.
    }

    /**
     * Auth token is used as username
     *
     * @param string $authToken
     * @return null|UserInterface
     */
    public function loadUserByAuthToken($authToken): ?UserInterface
    {
        if (!isset($authToken)) {
            return null;
        }

        $token = $this->em
            ->getRepository(AuthToken::class)
            ->findOneBy(['id' => AuthToken::hex2dec($authToken)]);

        if (!isset($token)) {
            return null;
        }

        return $token->getUser();
    }

    public
    function refreshUser(UserInterface $user)
    {
        // this is used for storing authentication in the session
        // but in this example, the token is sent in each request,
        // so authentication can be stateless. Throwing this exception
        // is proper to make things stateless
        throw new UnsupportedUserException();
    }

    public
    function supportsClass($class)
    {
        return User::class === $class;
    }
}
namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface, EquatableInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * This needs to be nullable because the email includes the id of newly created users, which can only be obtained after inserting the new record.
     * @ORM\Column(type="string", length=255, nullable=true, unique=true)
     * @Assert\Length(max=255)
     * @Assert\NotBlank()
     */
    private $email;

    /**
     * Set null to disable login
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\Length(max=255)
     * @Assert\NotBlank()
     */
    private $saltedPasswordHash;

    /**
     * @ORM\Column(type="datetime")
     */
    private $created;

    // ...

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\AuthToken", mappedBy="user", fetch="LAZY")
     */
    private $authTokens;

    /**
     * @ORM\Column(type="string", length=5)
     */
    private $role;

    // ...

    public function __construct()
    {
        $this->role = 'user';
        $this->saltedPasswordHash = null;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getSaltedPasswordHash(): ?string
    {
        return $this->saltedPasswordHash;
    }

    public function setSaltedPasswordHash(?string $saltedPasswordHash): self
    {
        $this->saltedPasswordHash = $saltedPasswordHash;

        return $this;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(?string $email): self
    {
        $this->email = $email;

        return $this;
    }

    public function getCreated(): ?\DateTimeInterface
    {
        return $this->created;
    }

    public function setCreated(\DateTimeInterface $created): self
    {
        $this->created = $created;

        return $this;
    }


    /**
     * @return ArrayCollection
     */
    public function getAuthTokens()
    {
        return $this->authTokens;
    }

    /**
     * @param ArrayCollection $authTokens
     * @return User
     */
    public function setAuthTokens(ArrayCollection $authTokens): User
    {
        $this->authTokens = $authTokens;

        return $this;
    }

    /**
     * @param AuthToken $authToken
     * @return User
     */
    public function addAuthToken(AuthToken $authToken): User
    {
        $this->authTokens->add($authToken);

        return $this;
    }

    /**
     * @param AuthToken $authToken
     * @return User
     */
    public function removeAuthToken(AuthToken $authToken): User
    {
        $this->authTokens->removeElement($authToken);

        return $this;
    }

    // ...

    /**
     * Returns the password used to authenticate the user.
     *
     * This should be the encoded password. On authentication, a plain-text
     * password will be salted, encoded, and then compared to this value.
     *
     * @return string The password
     */
    public function getPassword()
    {
        return $this->getSaltedPasswordHash();
    }

    /**
     * Returns the salt that was originally used to encode the password.
     *
     * This can return null if the password was not encoded using a salt.
     *
     * @return string|null The salt
     */
    public function getSalt()
    {
        // TODO: Implement getSalt() method.
    }

    /**
     * Returns the username used to authenticate the user.
     *
     * @return string The username
     */
    public function getUsername()
    {
        return $this->getEmail();
    }

    /**
     * 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()
    {
        $this->setSaltedPasswordHash('');
    }

    /**
     * @return mixed
     */
    public function getRole()
    {
        return $this->role;
    }

    /**
     * @param mixed $role
     * @return User
     */
    public function setRole($role): User
    {
        $this->role = $role;

        return $this;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return "User " . $this->email;
    }

    /**
     * The equality comparison should neither be done by referential equality
     * nor by comparing identities (i.e. getId() === getId()).
     *
     * However, you do not need to compare every attribute, but only those that
     * are relevant for assessing whether re-authentication is required.
     *
     * Also implementation should consider that $user instance may implement
     * the extended user interface `AdvancedUserInterface`.
     *
     * https://stackoverflow.com/a/39884792/6144818
     *
     * @param UserInterface $user
     * @return bool
     */
    public function isEqualTo(UserInterface $user)
    {
        return (
                $this->getUsername() == $user->getUsername()
            ) && (
                $this->getRoles() == $user->getRoles()
            );
    }

    // ...
}
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\AuthTokenRepository")
 */
class AuthToken
{
    /**
     * @ORM\Id()
     * @ORM\Column(type="decimal", precision=32, scale=0, options={"unsigned": true})
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="authTokens")
     */
    private $user;

    /**
     * @ORM\Column(type="datetime")
     */
    private $added;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $lastSeen;

    /**
     * @ORM\Column(type="string", length=12, nullable=true)
     */
    private $apiVersion;

    /**
     * @return string
     */
    public function getId(): string
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getHexId(): string
    {
        return $this->dec2hex($this->id);
    }

    /**
     * @param mixed $id
     */
    public function setId($id): void
    {
        $this->id = $id;
    }

    /**
     * @param mixed $id
     * @throws \Exception
     */
    public function generateId(): void
    {
        $length = 32;

        $str = "";
        $characters = range('0', '9');
        $max = count($characters) - 1;

        for ($i = 0; $i < $length; $i++) {
            $rand = random_int(0, $max);
            $str .= $characters[$rand];
        }

        $this->id = $str;
    }

    /**
     * @return mixed
     */
    public function getUser()
    {
        return $this->user;
    }

    /**
     * @param mixed $user
     */
    public function setUser($user): void
    {
        $this->user = $user;
    }

    /**
     * @return mixed
     */
    public function getAdded()
    {
        return $this->added;
    }

    /**
     * @param mixed $added
     */
    public function setAdded($added): void
    {
        $this->added = $added;
    }

    /**
     * @return mixed
     */
    public function getLastSeen()
    {
        return $this->lastSeen;
    }

    /**
     * @param mixed $lastSeen
     */
    public function setLastSeen($lastSeen): void
    {
        $this->lastSeen = $lastSeen;
    }

    public function getApiVersion(): ?string
    {
        return $this->apiVersion;
    }

    public function setApiVersion(string $apiVersion): self
    {
        $this->apiVersion = $apiVersion;

        return $this;
    }

    public static function dec2hex(string $dec): string
    {
        $hex = '';
        do {
            $last = bcmod($dec, 16);
            $hex = dechex($last) . $hex;
            $dec = bcdiv(bcsub($dec, $last), 16);
        } while ($dec > 0);
        return $hex;
    }

    public static function hex2dec($hex)
    {
        $dec = '0';
        $len = strlen($hex);
        for ($i = 1; $i <= $len; $i++)
            $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));

        return $dec;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return "AuthToken " . $this->id . " (" . $this->user . ")";
    }

}
Entitiy/AuthToken.php

namespace App\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;

class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
    public function createToken(Request $request, $providerKey)
    {
        $apiKey = $request->headers->get('authToken');

        if (!$apiKey) {
            throw new BadCredentialsException();

            // or to just skip api key authentication
            // return null;
        }

        return new PreAuthenticatedToken(
            'anon.',
            $apiKey,
            $providerKey
        );
    }

    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
    }

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        if (!$userProvider instanceof ApiKeyUserProvider) {
            throw new \InvalidArgumentException(
                sprintf(
                    'The user provider must be an instance of ApiKeyUserProvider (%s was given).',
                    get_class($userProvider)
                )
            );
        }

        $apiKey = $token->getCredentials();
        $username = $userProvider->getUsernameForApiKey($apiKey);

        if (!$username) {
            // CAUTION: this message will be returned to the client
            // (so don't put any un-trusted messages / error strings here)
            throw new BadCredentialsException(
                sprintf('API Key "%s" does not exist.', $apiKey)
            );
        }

        $user = $userProvider->loadUserByAuthToken($apiKey);

        if (!isset($user)) {
            throw new BadCredentialsException(
                sprintf('API Key "%s" does not exist.', $apiKey)
            );
        }

        // TODO: HERE, THE $user->getSaltedPasswordHash() RETURNS THE CORRECT VALUE!

        return new PreAuthenticatedToken(
            $user, // TODO: with "new User()" instead, it works!
            $apiKey,
            $providerKey,
            $user->getRoles()
        );
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        return new Response(
        // this contains information about *why* authentication failed
        // use it, or return your own message
            strtr($exception->getMessageKey(), $exception->getMessageData()),
            401
        );
    }
}
namespace App\Security;

use App\Entity\AuthToken;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class ApiKeyUserProvider implements UserProviderInterface
{
    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * ApiKeyUserProvider constructor.
     * @param EntityManagerInterface $em
     */
    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

    public function getUsernameForApiKey($apiKey)
    {
        return $apiKey;
    }

    public function loadUserByUsername($username)
    {
        // TODO: Implement loadUserByUsername() method.
    }

    /**
     * Auth token is used as username
     *
     * @param string $authToken
     * @return null|UserInterface
     */
    public function loadUserByAuthToken($authToken): ?UserInterface
    {
        if (!isset($authToken)) {
            return null;
        }

        $token = $this->em
            ->getRepository(AuthToken::class)
            ->findOneBy(['id' => AuthToken::hex2dec($authToken)]);

        if (!isset($token)) {
            return null;
        }

        return $token->getUser();
    }

    public
    function refreshUser(UserInterface $user)
    {
        // this is used for storing authentication in the session
        // but in this example, the token is sent in each request,
        // so authentication can be stateless. Throwing this exception
        // is proper to make things stateless
        throw new UnsupportedUserException();
    }

    public
    function supportsClass($class)
    {
        return User::class === $class;
    }
}
namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface, EquatableInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * This needs to be nullable because the email includes the id of newly created users, which can only be obtained after inserting the new record.
     * @ORM\Column(type="string", length=255, nullable=true, unique=true)
     * @Assert\Length(max=255)
     * @Assert\NotBlank()
     */
    private $email;

    /**
     * Set null to disable login
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\Length(max=255)
     * @Assert\NotBlank()
     */
    private $saltedPasswordHash;

    /**
     * @ORM\Column(type="datetime")
     */
    private $created;

    // ...

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\AuthToken", mappedBy="user", fetch="LAZY")
     */
    private $authTokens;

    /**
     * @ORM\Column(type="string", length=5)
     */
    private $role;

    // ...

    public function __construct()
    {
        $this->role = 'user';
        $this->saltedPasswordHash = null;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getSaltedPasswordHash(): ?string
    {
        return $this->saltedPasswordHash;
    }

    public function setSaltedPasswordHash(?string $saltedPasswordHash): self
    {
        $this->saltedPasswordHash = $saltedPasswordHash;

        return $this;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(?string $email): self
    {
        $this->email = $email;

        return $this;
    }

    public function getCreated(): ?\DateTimeInterface
    {
        return $this->created;
    }

    public function setCreated(\DateTimeInterface $created): self
    {
        $this->created = $created;

        return $this;
    }


    /**
     * @return ArrayCollection
     */
    public function getAuthTokens()
    {
        return $this->authTokens;
    }

    /**
     * @param ArrayCollection $authTokens
     * @return User
     */
    public function setAuthTokens(ArrayCollection $authTokens): User
    {
        $this->authTokens = $authTokens;

        return $this;
    }

    /**
     * @param AuthToken $authToken
     * @return User
     */
    public function addAuthToken(AuthToken $authToken): User
    {
        $this->authTokens->add($authToken);

        return $this;
    }

    /**
     * @param AuthToken $authToken
     * @return User
     */
    public function removeAuthToken(AuthToken $authToken): User
    {
        $this->authTokens->removeElement($authToken);

        return $this;
    }

    // ...

    /**
     * Returns the password used to authenticate the user.
     *
     * This should be the encoded password. On authentication, a plain-text
     * password will be salted, encoded, and then compared to this value.
     *
     * @return string The password
     */
    public function getPassword()
    {
        return $this->getSaltedPasswordHash();
    }

    /**
     * Returns the salt that was originally used to encode the password.
     *
     * This can return null if the password was not encoded using a salt.
     *
     * @return string|null The salt
     */
    public function getSalt()
    {
        // TODO: Implement getSalt() method.
    }

    /**
     * Returns the username used to authenticate the user.
     *
     * @return string The username
     */
    public function getUsername()
    {
        return $this->getEmail();
    }

    /**
     * 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()
    {
        $this->setSaltedPasswordHash('');
    }

    /**
     * @return mixed
     */
    public function getRole()
    {
        return $this->role;
    }

    /**
     * @param mixed $role
     * @return User
     */
    public function setRole($role): User
    {
        $this->role = $role;

        return $this;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return "User " . $this->email;
    }

    /**
     * The equality comparison should neither be done by referential equality
     * nor by comparing identities (i.e. getId() === getId()).
     *
     * However, you do not need to compare every attribute, but only those that
     * are relevant for assessing whether re-authentication is required.
     *
     * Also implementation should consider that $user instance may implement
     * the extended user interface `AdvancedUserInterface`.
     *
     * https://stackoverflow.com/a/39884792/6144818
     *
     * @param UserInterface $user
     * @return bool
     */
    public function isEqualTo(UserInterface $user)
    {
        return (
                $this->getUsername() == $user->getUsername()
            ) && (
                $this->getRoles() == $user->getRoles()
            );
    }

    // ...
}
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\AuthTokenRepository")
 */
class AuthToken
{
    /**
     * @ORM\Id()
     * @ORM\Column(type="decimal", precision=32, scale=0, options={"unsigned": true})
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="authTokens")
     */
    private $user;

    /**
     * @ORM\Column(type="datetime")
     */
    private $added;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $lastSeen;

    /**
     * @ORM\Column(type="string", length=12, nullable=true)
     */
    private $apiVersion;

    /**
     * @return string
     */
    public function getId(): string
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getHexId(): string
    {
        return $this->dec2hex($this->id);
    }

    /**
     * @param mixed $id
     */
    public function setId($id): void
    {
        $this->id = $id;
    }

    /**
     * @param mixed $id
     * @throws \Exception
     */
    public function generateId(): void
    {
        $length = 32;

        $str = "";
        $characters = range('0', '9');
        $max = count($characters) - 1;

        for ($i = 0; $i < $length; $i++) {
            $rand = random_int(0, $max);
            $str .= $characters[$rand];
        }

        $this->id = $str;
    }

    /**
     * @return mixed
     */
    public function getUser()
    {
        return $this->user;
    }

    /**
     * @param mixed $user
     */
    public function setUser($user): void
    {
        $this->user = $user;
    }

    /**
     * @return mixed
     */
    public function getAdded()
    {
        return $this->added;
    }

    /**
     * @param mixed $added
     */
    public function setAdded($added): void
    {
        $this->added = $added;
    }

    /**
     * @return mixed
     */
    public function getLastSeen()
    {
        return $this->lastSeen;
    }

    /**
     * @param mixed $lastSeen
     */
    public function setLastSeen($lastSeen): void
    {
        $this->lastSeen = $lastSeen;
    }

    public function getApiVersion(): ?string
    {
        return $this->apiVersion;
    }

    public function setApiVersion(string $apiVersion): self
    {
        $this->apiVersion = $apiVersion;

        return $this;
    }

    public static function dec2hex(string $dec): string
    {
        $hex = '';
        do {
            $last = bcmod($dec, 16);
            $hex = dechex($last) . $hex;
            $dec = bcdiv(bcsub($dec, $last), 16);
        } while ($dec > 0);
        return $hex;
    }

    public static function hex2dec($hex)
    {
        $dec = '0';
        $len = strlen($hex);
        for ($i = 1; $i <= $len; $i++)
            $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));

        return $dec;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return "AuthToken " . $this->id . " (" . $this->user . ")";
    }

}
namespace-App\Entity;
使用条令\ORM\Mapping作为ORM;
/**
*@ORM\Entity(repositoryClass=“App\Repository\AuthTokenRepository”)
*/
类AuthToken
{
/**
*@ORM\Id()
*@ORM\Column(type=“decimal”,精度=32,刻度=0,选项={“unsigned”:true})
*/
私人$id;
/**
*@ORM\manytone(targetEntity=“App\Entity\User”,inversedBy=“authTokens”)
*/
私人用户;
/**
*@ORM\Column(type=“datetime”)
*/
私人股本增加$;
/**
*@ORM\Column(type=“datetime”,nullable=true)
*/
私用$lastSeen;
/**
*@ORM\Column(type=“string”,length=12,nullable=true)
*/
私人版本;
/**
*@返回字符串
*/
公共函数getId():字符串
{
返回$this->id;
}
/**
*@返回字符串
*/
公共函数getHexId():字符串
{
返回$this->dec2hex($this->id);
}
/**
*@param混合$id
*/
公共函数setId($id):无效
{
$this->id=$id;
}
/**
*@param混合$id
*@throws\Exception
*/
公共函数generateId():void
{
$length=32;
$str=”“;
$characters=范围('0','9');
$max=计数($characters)-1;
对于($i=0;$i<$length;$i++){
$rand=random_int(0,$max);
$str.=$characters[$rand];
}
$this->id=$str;
}
/**
*@返回混合
*/
公共函数getUser()
{
返回$this->user;
}
/**
*@param混合$user
*/
公共函数setUser($user):无效
{
$this->user=$user;
}
/**
*@返回混合
*/
公共函数getAdded()
{
返回$this->added;
}
/**
*@param mixed$added
*/
公共函数setAdded($added):无效
{
$this->added=$added;
}
/**
*@返回混合
*/
公共函数getLastSeen()
{
返回$this->lastseed;
}
/**
*@param mixed$lastSeen
*/
公共函数setLastSeen($lastSeen):无效
{
$this->lastSeen=$lastSeen;
}
公共函数getApiVersion():?字符串
{
返回$this->apipversion;
}
公共函数setApiVersion(字符串$apiVersion):self
{
$this->apiVersion=$apiVersion;
退还$this;
}
公共静态函数dec2hex(字符串$dec):字符串
{
$hex='';
做{
$last=bcmod(12月16日);
$hex=dechex($last)。$hex;
$dec=bcdiv(bcsub($dec$last),16);
}而($dec>0);
返回$hex;
}
公共静态函数hex2dec($hex)
{
$dec='0';
$len=strlen($hex);
对于($i=1;$i id.)(“$this->user.”);
}
}
您的实体/User.php的此函数是您行为的原因:

/**
 * 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()
{
    $this->setSaltedPasswordHash('');
}
当身份验证在Symfony上起作用时,在身份验证之后,它不会泄漏敏感信息,甚至更糟糕的是,敏感信息不会在会话中结束


只要试着在该函数中对setter进行注释,您就会得到您期望的结果。

非常有用!非常感谢!