You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 12:25:40