密码未经过哈希处理,尝试连接 return 错误 500 Symfony
Password doesn't get hashed, and trying to connect return error 500 Symfony
总的来说,我对 Symfony 还很陌生,我主要使用它是因为我需要非常快速地做一些安全的事情,并且还要探索 Symfony 4。
我正在尝试与安全配方建立安全连接,但我面临两个主要问题(可能相关)和一个小问题。
首先,我尝试将 salt 定义为可为 null,但它在 db 中仍然是 NOT NULL
。这是我对列的定义:
/**
* @ORM\Column(name="salt", type="string", nullable=true)
*/
private $salt;
所以现在的大问题是:我添加的密码未经过哈希处理并尝试连接 returns 错误 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()" requires that you provide a value for the "$encoder" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one."
我也尝试 copy/paste(我是多么绝望...)我的 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);
}
我正在从服务器获取日志:
"No encoder has been configured for account "App\Entity\User"."
我也尝试直接在我的数据库中插入一些值,但是在尝试连接时输入正确的密码时出现 "Access Denied" 消息,我认为这是另一个问题...
我真的不明白我哪里错了,我找不到人问这个问题。如果您能帮助我,我将不胜感激。
注意:
UserController 路由以 /user 开头,完全 public 因为我需要用户访问安全管理面板。
编辑
我正在使用 MySQL 5.7 和 PHP 7.2,如果可以相关的话
由于您使用 Argon2i 作为实体的编码器算法,因此您的 $salt
已过时:
Do you need to use a Salt property?
If you use bcrypt or argon2i, no. Otherwise, yes. All passwords must be hashed with a salt, but bcrypt and argon2i do this internally [...] the getSalt() method in User can just return null (it's not used). [...]
-How to Load Security Users from the Database (the Entity Provider)
尝试删除 $salt
属性 和 setter 方法,让您的 getSalt()
return null
。持久化用户不进行编码操作,并检查持久化的密码。
虽然这可以看作是一种肮脏的黑客行为,但这似乎是一种很好的做法...
感谢@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
现在添加用户效果很好。我仍然有连接问题,但没有抛出异常
总的来说,我对 Symfony 还很陌生,我主要使用它是因为我需要非常快速地做一些安全的事情,并且还要探索 Symfony 4。
我正在尝试与安全配方建立安全连接,但我面临两个主要问题(可能相关)和一个小问题。
首先,我尝试将 salt 定义为可为 null,但它在 db 中仍然是 NOT NULL
。这是我对列的定义:
/**
* @ORM\Column(name="salt", type="string", nullable=true)
*/
private $salt;
所以现在的大问题是:我添加的密码未经过哈希处理并尝试连接 returns 错误 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()" requires that you provide a value for the "$encoder" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one."
我也尝试 copy/paste(我是多么绝望...)我的 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);
}
我正在从服务器获取日志:
"No encoder has been configured for account "App\Entity\User"."
我也尝试直接在我的数据库中插入一些值,但是在尝试连接时输入正确的密码时出现 "Access Denied" 消息,我认为这是另一个问题...
我真的不明白我哪里错了,我找不到人问这个问题。如果您能帮助我,我将不胜感激。
注意: UserController 路由以 /user 开头,完全 public 因为我需要用户访问安全管理面板。
编辑 我正在使用 MySQL 5.7 和 PHP 7.2,如果可以相关的话
由于您使用 Argon2i 作为实体的编码器算法,因此您的 $salt
已过时:
Do you need to use a Salt property?
If you use bcrypt or argon2i, no. Otherwise, yes. All passwords must be hashed with a salt, but bcrypt and argon2i do this internally [...] the getSalt() method in User can just return null (it's not used). [...]
-How to Load Security Users from the Database (the Entity Provider)
尝试删除 $salt
属性 和 setter 方法,让您的 getSalt()
return null
。持久化用户不进行编码操作,并检查持久化的密码。
虽然这可以看作是一种肮脏的黑客行为,但这似乎是一种很好的做法...
感谢@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
现在添加用户效果很好。我仍然有连接问题,但没有抛出异常