Миграция пользователей на безопасный алгоритм хеширования в Symfony
Ваше приложение может использовать старый, небезопасный алгоритм хеширования для хранения пароля, такой как MD5 (без использования соли)
Эта статья объясняет как преобразовать уже имеющиеся пароли, зашифрованные уязвимым алгоритмом в пароли зашифрованные с использованием безопасного метода хеширования (например с использованием Bcrypt )
Что бы решить проблему, мы сделаем конвертацию на лету, когда пользователь успешно входит в систему. Будем использовать интерфейс EncoderAwareInterface
login listener и использовать не очень хорошо известные параметры в security.yml.
Аутентификация до миграции
Если ваше приложение использует зашифрованные при помощи MD5 пароли, файл security.yml будет выглядеть примерно так что бы аутентификация Symfony работала
# app/config/security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: md5
encode_as_base64: false
iterations: 1
В этой статье, мы предполагаем что сущность User выглядит следующим образом:
# src/AppBundle/Entity/User.php Все пользователи могут выполнять вход, вне зависимости от используемого алгоритма шифрования.
Добавляем «слушатель», который будет выполнять миграцию
Мы присоединим слушатель на событие Symfony security.interactive_login это событие срабатывает, когда пользователь выполнил успешный вход.
Для начала определим этот слушатель в файле services.yml file:
Declare first the listener in the services.yml file:
# app/config/services.yml
services:
app.login_listener:
class: AppBundle\EventListener\LoginListener
tags:
- { name: kernel.event_listener, event: security.interactive_login }
arguments:
- "@security.encoder_factory"
- "@doctrine.orm.entity_manager"
Создадим слушатель
# src/AppBundle/EventListener/LoginListener.php
<?php namespace AppBundle\EventListener; use Doctrine\Common\Persistence\ObjectManager; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; class LoginListener { private $encoderFactory; private $om; public function __construct(EncoderFactoryInterface $encoderFactory, ObjectManager $om) { $this->encoderFactory = $encoderFactory;
$this->om = $om;
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
$token = $event->getAuthenticationToken();
// Migrate the user to the new hashing algorithm if is using the legacy one
if ($user->hasLegacyPassword()) {
// Credentials can be retrieved thanks to the false value of
// the erase_credentials parameter in security.yml
$plainPassword = $token->getCredentials();
$user->setOldPassword(null);
$encoder = $this->encoderFactory->getEncoder($user);
$user->setPassword(
$encoder->encodePassword($plainPassword, $user->getSalt())
);
$this->om->persist($user);
$this->om->flush();
}
// We don't need any more credentials
$token->eraseCredentials();
}
}
Этот слушатель обновляет пароль пользователь только в случае если пользователь все еще использует устаревшую систему паролей.
Что бы перекодировать пароль, нам нужен незахешированный пароль, введенный пользователем, который по-умолчанию недоступен в authentication token предоставленный объектом InteractiveLoginEvent. Что бы сделать его доступным, сделайте следующие изменения в файле security.yml:
# app/config/security.yml
security:
erase_credentials: false
# ...
Как только ваши пользователи будут входить, данные о них будут обновлены в базе данных, делая их более безопасными со временем. Однажды, основная часть пользователей хотя бы раз выполнит вход и вы сможете удалить колонку old_password и реализовать фичу «Забыли пароль?» для тех, кто не был мигирован.
Автор: Michaël Perrin, ссылка на оригинальный пост