Symfony 6.1无法使用3.4用户数据库登录问题求助
Symfony 6.1 沿用旧Symfony 3.4用户数据库登录失败问题
我从零搭建了Symfony 6.1站点并配置Security组件,尝试沿用旧Symfony 3.4站点的用户数据库(包含现有SHA1密码哈希与盐值),但登录时始终返回错误:The presented password is invalid。已严格按照官方文档配置,未进行其他额外设置,相关配置及代码如下:
错误栈信息
#message: "The presented password is invalid." #code: 0 #file: "/** redacted **/vendor/symfony/security-http/EventListener/CheckCredentialsListener.php" #line: 69 #serialized: null -token: null trace: {▼ /** redacted **/vendor/symfony/security-http/EventListener/CheckCredentialsListener.php:69 {▶} /** redacted **/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php:175 {▶} /** redacted **/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php:326 {▶} /** redacted **/vendor/symfony/http-foundation/Session/Session.php:258 {▶} /** redacted **/vendor/symfony/http-foundation/Session/Session.php:278 {▶} /** redacted **/vendor/symfony/http-foundation/Session/Session.php:70 {▶} /** redacted **/vendor/symfony/security-http/Authentication/AuthenticationUtils.php:40 {▶} /** redacted **/src/Controller/SecurityController.php:33 {▶} /** redacted **/vendor/symfony/http-kernel/HttpKernel.php:153 {▶} /** redacted **/vendor/symfony/http-kernel/HttpKernel.php:75 {▶} /** redacted **/vendor/symfony/http-kernel/Kernel.php:202 {▶} /** redacted **/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php:35 {▶} /** redacted **/vendor/autoload_runtime.php:29 {▶} /** redacted **/public/index.php:5 {▶} } }
security.yaml配置
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords password_hashers: App\Entity\Users: algorithm: sha1 iterations: 1 encode_as_base64: false # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: app_user_provider: entity: class: App\Entity\Users property: email firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: lazy: true provider: app_user_provider form_login: login_path: login check_path: login enable_csrf: false login_throttling: max_attempts: 3 # per minute interval: '15 minutes'
SecurityController.php代码
namespace App\Controller; use App\Entity\Office; use Doctrine\Persistence\ManagerRegistry; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use Psr\Log\LoggerInterface; class SecurityController extends AbstractController { public function __construct(private ManagerRegistry $doctrine, private LoggerInterface $logger) {} #[Route('/login', name: 'login')] public function login(Request $request, AuthenticationUtils $authenticationUtils): Response { $sessionStorage = new NativeSessionStorage([], new NativeFileSessionHandler()); $session = new Session($sessionStorage); $doctrine = $this->doctrine; $logger = $this->logger; $logger->info('loginAction'); // get the login error if there is one $error = $authenticationUtils->getLastAuthenticationError(); // last username entered by the user $lastUsername = $authenticationUtils->getLastUsername(); return $this->render( 'security/login.html.twig', [ 'controller_name' => 'SecurityController', // last username entered by the user 'last_username' => $lastUsername, 'error' => $error, ] ); } }
login.html.twig代码
{% block PageHeader %}{% endblock %} {% block PageContent %} <div style="min-height: 250px;"> <h1>{{ 'security.login.title'|trans({}) }}</h1> <br /> {% if error %} {{ dump(error) }} {% if error.messageKey is defined %} <div class="error">{{ error.messageKey|trans(error.messageData) }}</div> {% endif %} {% endif %} <form action="{{ path('login') }}" method="post" class="login"> <label for="username">{{ 'security.login.username'|trans({}) }}</label> <input type="text" id="username" name="_username" value="{{ last_username }}" required="required" /> <br /><br /> <label for="password">{{ 'security.login.password'|trans({}) }}</label> <input type="password" id="password" name="_password" required="required" /> <br /> <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}"> <input type="submit" id="_submit" name="_submit" value="{{ 'security.login.submit'|trans({}) }}" /> </form> </div> {% endblock %} {% block PageFooter %}{% endblock %}
Users实体类代码
namespace App\Entity; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use App\Repository\UsersRepository; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; /** * Users * * @ORM\Table(name="Users", indexes={@ORM\Index(name="IDX_D5428AED73FD6E34", columns={"Office"})}) * @ORM\Entity(repositoryClass="App\Repository\UsersRepository") */ class Users implements UserInterface, PasswordAuthenticatedUserInterface { /** * @var int * * @ORM\Column(name="UserId", type="integer", nullable=false) * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") */ private $userid; /** * @var string|null * * @ORM\Column(name="Firstname", type="string", length=100, nullable=true) */ private $firstname; /** * @var string|null * * @ORM\Column(name="Surname", type="string", length=100, nullable=true) */ private $surname; /** * @var string * * @ORM\Column(name="Email", type="string", length=150, nullable=false) */ private $email; /** * @var string|null * * @ORM\Column(name="JobTitle", type="string", length=150, nullable=true) */ private $jobtitle; /** * @var string|null * * @ORM\Column(name="Password", type="string", length=150, nullable=true) */ private $password; /** * @var string|null * * @ORM\Column(name="Salt", type="string", length=50, nullable=true) */ private $salt; /** * @var int|null * * @ORM\Column(name="Status", type="integer", nullable=true) */ private $status; /** * @var \DateTime|null * * @ORM\Column(name="CreatedOn", type="datetime", nullable=true) */ private $createdon; /** * @var int|null * * @ORM\Column(name="CreatedBy", type="integer", nullable=true) */ private $createdby; /** * @var \DateTime|null * * @ORM\Column(name="LastUpdatedOn", type="datetime", nullable=true) */ private $lastupdatedon; /** * @var int|null * * @ORM\Column(name="LastUpdatedBy", type="integer", nullable=true) */ private $lastupdatedby; /** * @var \DateTime|null * * @ORM\Column(name="Deleted", type="datetime", nullable=true) */ private $deleted; /** * @var \Office * * @ORM\ManyToOne(targetEntity="Office") * @ORM\JoinColumns({ * @ORM\JoinColumn(name="Office", referencedColumnName="OfficeId") * }) */ private $office; /** * @var \Doctrine\Common\Collections\Collection * * @ORM\ManyToMany(targetEntity="Usergroup", inversedBy="userid") * @ORM\JoinTable(name="usergroupmap", * joinColumns={ * @ORM\JoinColumn(name="UserId", referencedColumnName="UserId") * }, * inverseJoinColumns={ * @ORM\JoinColumn(name="UserGroup", referencedColumnName="UserGroupId") * } * ) */ private $usergroup = array(); /** * Constructor */ public function __construct() { $this->usergroup = new \Doctrine\Common\Collections\ArrayCollection(); } public function getUserid(): ?int { return $this->userid; } public function getFirstname(): ?string { return $this->firstname; } public function setFirstname(?string $firstname): self { $this->firstname = $firstname; return $this; } public function getSurname(): ?string { return $this->surname; } public function setSurname(?string $surname): self { $this->surname = $surname; return $this; } public function getEmail(): ?string { return $this->email; } public function setEmail(string $email): self { $this->email = $email; return $this; } public function getJobtitle(): ?string { return $this->jobtitle; } public function setJobtitle(?string $jobtitle): self { $this->jobtitle = $jobtitle; return $this; } public function getPassword(): ?string { return $this->password; } public function setPassword(?string $password): self { $this->password = $password; return $this; } public function getSalt(): ?string { return $this->salt; } public function setSalt(?string $salt): self { $this->salt = $salt; return $this; } public function getStatus(): ?int { return $this->status; } public function setStatus(?int $status): self { $this->status = $status; return $this; } public function getCreatedon(): ?\DateTimeInterface { return $this->createdon; } public function setCreatedon(?\DateTimeInterface $createdon): self { $this->createdon = $createdon; return $this; } public function getCreatedby(): ?int { return $this->createdby; } public function setCreatedby(?int $createdby): self { $this->createdby = $createdby; return $this; } public function getLastupdatedon(): ?\DateTimeInterface { return $this->lastupdatedon; } public function setLastupdatedon(?\DateTimeInterface $lastupdatedon): self { $this->lastupdatedon = $lastupdatedon; return $this; } public function getLastupdatedby(): ?int { return $this->lastupdatedby; } public function setLastupdatedby(?int $lastupdatedby): self { $this->lastupdatedby = $lastupdatedby; return $this; } public function getDeleted(): ?\DateTimeInterface { return $this->deleted; } public function setDeleted(?\DateTimeInterface $deleted): self { $this->deleted = $deleted; return $this; } public function getOffice(): ?Office { return $this->office; } public function setOffice(?Office $office): self { $this->office = $office; return $this; } /** * @return Collection<int, Usergroup> */ public function getUsergroup(): Collection { return $this->usergroup; } public function addUsergroup(Usergroup $usergroup): self { if (!$this->usergroup->contains($usergroup)) { $this->usergroup->add($usergroup); } return $this; } public function removeUsergroup(Usergroup $usergroup): self { $this->usergroup->removeElement($usergroup); return $this; } /** * @see UserInterface */ public function eraseCredentials(): array { // If you store any temporary, sensitive data on the user, clear it here } /** * @see UserInterface */ public function getRoles(): array { foreach ($this->usergroup as $key => $value) { $roles[] = $value->getUsergroup(); } return array_unique($roles); } /** * A visual identifier that represents this user. * * @see UserInterface */ public function getUserIdentifier(): string { return (string) $this->email; } }
解决思路
- 核对盐值拼接顺序:Symfony 3.4的SHA1加盐哈希逻辑一般是
sha1(明文密码 + 盐值),但Symfony 6默认可能采用sha1(盐值 + 明文密码)的顺序。需要确认旧系统的哈希生成规则,若顺序不符,需自定义密码哈希器匹配旧逻辑。 - 检查用户状态:确认数据库中
Status字段值是否为启用状态(比如旧系统中1代表启用),Security组件会拒绝状态异常的用户登录,即便密码正确。 - 修正
eraseCredentials方法:当前实体中该方法错误返回数组,但UserInterface要求返回void,这会导致内部数据处理异常,修正代码:public function eraseCredentials(): void { // 若存储临时敏感数据,在此清除 } - 删除手动创建Session的代码:SecurityController中手动实例化Session的代码会覆盖Symfony默认Session服务,导致登录状态无法正常保存,删除以下代码:
$sessionStorage = new NativeSessionStorage([], new NativeFileSessionHandler()); $session = new Session($sessionStorage); - 验证密码字段完整性:检查数据库中
Password字段的哈希值是否完整,无截断或编码问题;同时确认实体getPassword()方法正确返回该字段值。 - 保持CSRF配置一致:security.yaml中已设置
enable_csrf: false,但登录表单仍保留了CSRF令牌字段,需删除该字段避免干扰:<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

