Reputation: 479
I am trying to integrate my legacy database password validator, for that I have configured a custom encoding password: https://symfony.com/doc/current/security/named_encoders.html
I am using symfony 5.1 and php 7.4.
It is my security.yaml
security:
encoders:
App\Entity\User:
algorithm: auto
#para oracle puse auto
app_encoder:
id: 'App\Security\Encoder\MyCustomPasswordEncoder'
#para oracle
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
provider: app_user_provider
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: app_logout
#target: app_logout
remember_me:
secret: '%kernel.secret%'
lifetime: 2592000 # 30 days in seconds
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# 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 }
# - { path: ^/profile, roles: ROLE_USER }
This is my password custom encoder src/Security/Encoder/MyCustomPasswordEncoder.php
<?php
namespace App\Security\Encoder;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class MyCustomPasswordEncoder implements UserPasswordEncoderInterface
{
/**
* {@inheritdoc}
*/
public function encodePassword(UserInterface $user, string $plainPassword)
{
$encoder = $this->encoderFactory->getEncoder($user);
return $encoder->encodePassword($plainPassword, $user->getSalt());
}
/**
* {@inheritdoc}
*/
public function isPasswordValid(UserInterface $user, string $raw)
{
if (null === $user->getPassword()) {
return false;
}
die('Esta usando la mia');
$encoder = $this->encoderFactory->getEncoder($user);
return $encoder->isPasswordValid($user->getPassword(), $raw, $user->getSalt());
}
/**
* {@inheritdoc}
*/
public function needsRehash(UserInterface $user): bool
{
if (null === $user->getPassword()) {
return false;
}
$encoder = $this->encoderFactory->getEncoder($user);
return $encoder->needsRehash($user->getPassword());
}
}
this is my src/Security/LoginFormAuthenticator.php
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use App\Repository\UserRepository;
use Symfony\Component\Routing\RouterInterface; //segudo parametro constructor
use Symfony\Component\Security\Core\Security; //Security::
use Symfony\Component\HttpFoundation\RedirectResponse; //redirect response
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; //CSR Token
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; //password
use App\Security\Encoder\MyCustomPasswordEncoder;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
private $userRepository;
private $router;
private $csrfTokenManager;
private $passwordEncoder;
public function __construct(UserRepository $userRepository, RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->userRepository = $userRepository;
$this->router = $router;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
public function supports(Request $request)
{
return $request->attributes->get('_route') === 'app_login'
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
// todo
//dd($request->request->all()); //esto es lo mismo que die(dump())
/*return [
'email' => $request->request->get('email'),
'password' => $request->request->get('password'),
];*/
$credentials = [
'email' => $request->request->get('email'),
'csrf_token' => $request->request->get('_csrf_token'),
'password' => $request->request->get('password'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['email']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
// todo
//dd($credentials);
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
return $this->userRepository->findOneBy(['email' => $credentials['email']]);
}
public function checkCredentials($credentials, UserInterface $user)
{
// todo
//dd($user);
//return true;
//dd($this);
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
// todo
//dd('Success');
return new RedirectResponse($this->router->generate('app_homepage'));
}
protected function getLoginUrl()
{
// TODO: Implement getLoginUrl() method.
return $this->router->generate('app_login');
}
}
My problem is that it does not run my custom password validator it is taking the default password validator.
Thank you.
Upvotes: 2
Views: 2437
Reputation: 48893
Things can get confusing because there are two interfaces involved: PasswordEncoderInterface and UserPasswordEncoderInterface. There is a tendency to want to create a custom UserPasswordEncoderInterface because, well, you are encoding a user password. But in fact the UserPasswordEncoder object is basically just a wrapper for the underlying PasswordEncoders.
So you need to implement your legacy database password validator in a PasswordEncoder object:
namespace App\Security;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
class MyPasswordEncoder implements PasswordEncoderInterface
{
public function encodePassword(string $raw, ?string $salt)
{
return 'ENCODED' . $raw;
}
public function isPasswordValid(string $encoded, string $raw, ?string $salt)
{
return true;
}
public function needsRehash(string $encoded): bool
{
return false;
}
}
Next you need to tell Symfony to use your custom encoder for a given type of user:
# config/packages/security.yaml
security:
encoders:
App\Entity\User:
id: App\Security\MyPasswordEncoder
At this point you can confirm that your encoder is being used with:
$ bin/console security:encode-password xxx
Encoder used App\Security\MyPasswordEncoder
Encoded password ENCODEDxxx
That should be enough to get your going. But at the risk of adding even more confusion, here is a little test command which attempts to show the relationship UserPasswordEncoderInterface, PasswordEncoderInterface and the EncoderFactoryInterface which essentially picks the correct encoder for a given user based on the security.yaml mappings:
class UserCommand extends Command
{
protected static $defaultName = 'app:user';
private $encoderFactory;
private $userPasswordEncoder;
public function __construct(EncoderFactoryInterface $encoderFactory, UserPasswordEncoderInterface $userPasswordEncoder)
{
parent::__construct();
$this->encoderFactory = $encoderFactory;
$this->userPasswordEncoder = $userPasswordEncoder;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$encoder = $this->encoderFactory->getEncoder(User::class);
echo get_class($encoder) . "\n";
$user = new User();
$encoded = $this->userPasswordEncoder->encodePassword($user,'zzz');
echo $encoded . "\n";
return Command::SUCCESS;
}
}
Also wanted to point out that the link in the question points to using named encoders. Named encoders allow mapping multiple encoders to a single entity class and then allowing the entity to pick the encoder based on some property. For example, an admin user might use a different encoder than a regular user. Named encoders are not applicable to this use case.
You might however want to take a look at how to automatically upgrade passwords. Once configured users can login with the legacy encoder and then be automatically updated to a new encoder.
Upvotes: 2