【问题标题】:Password doesn't get hashed, and trying to connect return error 500 Symfony密码没有得到散列,并尝试连接返回错误 500 Symfony
【发布时间】:2018-10-14 10:41:10
【问题描述】:

总的来说,我对 Symfony 还是很陌生,我主要使用它是因为我需要非常快速地做一些安全的事情,并且还需要发现 Symfony 4。

我正在尝试与安全配方建立安全连接,但我面临两个主要问题(可能相关)和一个小问题。

首先,我尝试将 salt 定义为可为空,但它在 db 中仍然是 NOT NULL。这是我对列的定义:

/**
 * @ORM\Column(name="salt", type="string", nullable=true)
 */
private $salt;

所以现在最大的问题是:我添加的密码没有经过哈希处理,尝试连接会返回错误 500

我尝试按照文档进行操作,以下是: 我的实体

use Doctrine\ORM\Mapping as ORM;
use PhpParser\Node\Scalar\String_;
use Symfony\Component\Security\Core\User\UserInterface;

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

    /**
     * @ORM\Column(type="string", length=25, unique=true)
     */
    private $username;

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

    /**
     * @ORM\Column(type="string", length=254, unique=true, nullable=true)
     */
    private $email;

    /**
     * @ORM\Column(name="is_active", type="boolean")
     */
    private $isActive;

    /**
     * @ORM\Column(name="salt", type="string", nullable=true)
     */
    private $salt;

    /**
     * @ORM\Column(name="alias", type="string")
     */
    private $alias;

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

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

    public function __construct()
    {
        $this->isActive = true;
        // may not be needed, see section on salt below
//        $this->salt = md5(uniqid('', true));
    }

    public function getUsername()
    {
        return $this->username;
    }

    public function getSalt() :String
    {
        // you *may* need a real salt depending on your encoder
        // see section on salt below
        return $this->salt;
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function getRoles()
    {
        return array('ROLE_USER');
    }

    public function eraseCredentials()
    {
    }

    /** @see \Serializable::serialize() */
    public function serialize()
    {
        return serialize([
            $this->id,
            $this->username,
            $this->password,
            // see section on salt below
//            $this->salt
        ]);
    }

    /** @see \Serializable::unserialize() */
    public function unserialize($serialized)
    {
        list (
            $this->id,
            $this->username,
            $this->password,
            // see section on salt below
//            $this->salt
            ) = unserialize($serialized, ['allowed_classes' => false]);
    }

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

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

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

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

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

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

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

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

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

我的控制器

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class SecurityController extends Controller
{
    /**
     * @Route("/login", name="login")
     */
    public function login(Request $request, AuthenticationUtils $authenticationUtils)
    {
        // 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', array(
            'last_username' => $lastUsername,
            'error'         => $error,
        ));
    }
}

use App\Entity\User;
use App\Form\UserType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;


/**
 * @Route("/user")
 */
class UserController extends Controller
{
    /**
     * @Route("/", name="user_index", methods="GET")
     */
    public function index(UserRepository $userRepository): Response
    {
        return $this->render('user/index.html.twig', ['users' => $userRepository->findAll()]);
    }

    /**
     * @Route("/new", name="user_new", methods="GET|POST")
     */
    public function new(Request $request): Response
    {
        $user = new User();
        $form = $this->createForm(UserType::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($user);
            $em->flush();

            return $this->redirectToRoute('user_index');
        }

        return $this->render('user/new.html.twig', [
            'user' => $user,
            'form' => $form->createView(),
        ]);
    }

    /**
     * @Route("/{id}", name="user_show", methods="GET")
     */
    public function show(User $user): Response
    {
        return $this->render('user/show.html.twig', ['user' => $user]);
    }

    /**
     * @Route("/{id}/edit", name="user_edit", methods="GET|POST")
     */
    public function edit(Request $request, User $user): Response
    {
        $form = $this->createForm(UserType::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->getDoctrine()->getManager()->flush();

            return $this->redirectToRoute('user_edit', ['id' => $user->getId()]);
        }

        return $this->render('user/edit.html.twig', [
            'user' => $user,
            'form' => $form->createView(),
        ]);
    }

    /**
     * @Route("/{id}", name="user_delete", methods="DELETE")
     */
    public function delete(Request $request, User $user): Response
    {
        if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) {
            $em = $this->getDoctrine()->getManager();
            $em->remove($user);
            $em->flush();
        }

        return $this->redirectToRoute('user_index');
    }

    public function register(User $user, UserPasswordEncoderInterface $encoder)
    {
        $plainPassword = $user->getPassword();
        $encoded = $encoder->encodePassword($user, $plainPassword);
        $user->setPassword($encoded);
    }
}

还有我的 security.yaml

security:
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        db_provider:
            entity:
                class: App\Entity\User
                property: username

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: ~
            provider: db_provider
            form_login:
                login_path: login
                check_path: login
            logout:
                path:   /logout
                target: /homepage
            pattern:    ^/admin
            http_basic: ~

    encoders:
        App\Entity\User:
            algorithm: argon2i

    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }

在我的 UserController::new() 中检查 isSubmited 和 isValid 后,我尝试添加它

$plainPassword = $user->getPassword;
$encoded = $encoder->encodePassword($user, $plainPassword);
$user->setPassword($encoded);

但我有一个错误说我作为方法参数传递的UserPasswordEncoderInterface $encoder 在加载表单时没有注入。我仍然不确定它是否是一个很好的解决方案,因为我必须在 UserController::edit() 中复制该逻辑,这看起来不像 Symfony 的代码。

(错误:)

"Controller "App\Controller\UserController::new()" 要求您为 "$encoder" 参数提供一个值。参数可以为 null 并且没有提供 null 值,也没有提供默认值或者因为在这个之后有一个非可选参数。”

我还尝试复制/粘贴(我多么绝望……)我的 UserController 和 SecurityController 中的代码,但这也不起作用

public function register(UserPasswordEncoderInterface $encoder)
{
    // whatever *your* User object is
    $user = new App\Entity\User();
    $plainPassword = 'ryanpass';
    $encoded = $encoder->encodePassword($user, $plainPassword);

    $user->setPassword($encoded);
}

我从服务器获取此日志:

“没有为帐户“App\Entity\User”配置编码器。”

我也尝试直接在我的数据库中插入一些值,但是在输入正确密码时尝试连接给了我“拒绝访问”消息,我认为这是另一个问题...

我真的不明白我哪里错了,我找不到人问这个问题。如果您能帮助我,我将不胜感激。

注意: UserController 路由以 /user 开头并且是完全公开的,因为我需要一个用户来访问安全的管理面板。

编辑 如果可以的话,我正在使用 MySQL 5.7 和 PHP 7.2

【问题讨论】:

  • @MathieuDormeval,感谢您的回答,但至少目前看来这不是一个很好的解决方案。当我在他们的 Github 上阅读问题时,依赖这个 repo 不是一种安全的编码方式(但是?)
  • 您的项目是否要求您将盐存储在数据库中?
  • @LeonWillens 根据文档,一点也不。但我必须有它来实现 UserInterface
  • @Zyigh 如果您的答案与接受的答案明显不同,您必须添加另一个答案(如果它包含更好的解决方案,则接受它)或编辑现有答案。您应该切勿在问题中添加答案,因此请记住删除该部分。

标签: php symfony symfony4


【解决方案1】:

由于您使用 Argon2i 作为实体的编码器算法,因此您的 $salt 已过时:

您需要使用 Salt 属性吗?

如果您使用 bcrypt 或 argon2i,则不会。否则,是的。所有密码都必须使用盐进行哈希处理,但 bcrypt 和 argon2i 在内部执行此操作 [...] User 中的 getSalt() 方法只能返回 null (未使用)。 [...]

-How to Load Security Users from the Database (the Entity Provider)

尝试删除$salt 属性和setter 方法,让您的getSalt() 返回null。持久化用户不进行编码操作,检查持久化密码。

虽然这可以被视为一种肮脏的黑客行为,但它似乎是一种很好的做法......

【讨论】:

  • 非常感谢。我标记我已解决,因为寻找更多解释给了我大部分解决方案。我正在编辑问题以写出我找到的完整答案。我永远不会想到这一点,因为它看起来像一个“肮脏的黑客”(很好的描述)。
【解决方案2】:

感谢@LeonWillens,我终于找到了解决方案。实际上删除 salt 属性和 setter 让我发现安全配方没有验证器。所以我跑了composer require doctrine form security validator。我在我的实体中添加了一个不是列的纯文本字段

/**
 * @Assert\NotBlank()
 * @Assert\Length(max=4096)
 */
private $plainPassword;

这样,我可以在 UserController::new() 中添加这个逻辑

/**
 * @Route("/new", name="user_new", methods="GET|POST")
 */
public function new(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response
{
    $user = new User();
    $form = $this->createForm(UserType::class, $user);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
        $user->setPassword($password);
        $em = $this->getDoctrine()->getManager();
        $em->persist($user);
        $em->flush();

        return $this->redirectToRoute('user_index');
    }

    return $this->render('user/new.html.twig', [
        'user' => $user,
        'form' => $form->createView(),
    ]);
}

我在 security.yaml 中更改了编码器

encoders:
    Symfony\Component\Security\Core\User\User: plaintext
    App\Entity\User:
        algorithm: argon2i

现在添加用户可以完美运行。我仍然有连接问题,但没有抛出异常之类的东西

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-28
    • 1970-01-01
    • 2018-10-02
    • 2010-11-08
    • 2021-04-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多