Теория: есть ли способ сохранить пользователя Ldap в вашей собственной базе данных, обязательно для отношений, без пароля и каким-то образом заставить RefreshUser работать?
Я изменил свою систему, чтобы она работала без сохранения pwd, проверяя при каждом входе в систему, соответствует ли пароль паролю пользователя сервера ldap.
Symfony вызывает при каждом запросе метод refreshUser
вашего провайдера, у меня есть собственный LDAP-провайдер, чтобы обновить пользователя, используя доктрину из базы данных. Symfony гарантирует, что объект всегда будет обновлен после редактирования.
Проблема в том, что этот метод вызывает метод User Entity getPassword()
, который не реализован из-за отсутствия pwd в db.
Я видел способ исправить это, который сохраняет объект в сеансе и каждый раз, когда вызывается refreshUser()
, возвращает объект сеанса. Но тогда нет никакого способа, чтобы он всегда обновлялся после редактирования. И я не хочу разрушать инфраструктуру Symfony.
Итак, есть ли способ refreshUser()
, возможно, снова вызвать на сервер LDAP и спросить, совпадает ли информация?
-РЕШЕНО-
Любой, у кого есть приложение, которое использует аутентификацию сервера ldap и пользовательскую сущность пользователя в базе данных вашего приложения по причинам, но вы не хотите добавлять пароль из соображений безопасности. Вот один ответ:
0. Удалить эквивалент атрибута pwd внутри db
Внутри вашего пользовательского объекта удалите аннотации @Орм, чтобы не сохранять pwd в вашей базе данных. Сохраните атрибут и Getter/Setter.
Например:
//...
private string $password;
//...
1. Добавьте пользовательский LdapProvider
Создайте новый класс, который расширяет Symfony\Component\Ldap\Security\LdapUserProvider
. Возможно, вам придется обрабатывать DependencyInjections вручную, добавляя свой собственный Provider внутри вашего services.yaml:
fullyQualified\NameSpace\CustomLdapProvider:
arguments:
- '@Doctrine\ORM\EntityManagerInterface' # I need the EM in __construct
- '@Symfony\Component\Ldap\Ldap'
- # baseDn
- # searchDN
- # searchPassword
- # roles
- # uidKey
- # filter optionaly, add null
- # passwordAttribute
Переопределите методы «supportsClass» и «refreshUser» следующим кодом:
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
return $this->entityManager->getRepository(User::class)->find($user);
}
public function supportsClass(string $class): bool
{
return User::class === $class || is_subclass_of($class, User::class) || LdapUser::class === $class || is_subclass_of($class, LdapUser::class);
}
Ваш метод refreshUser()
должен возвращать объект пользователя. В моем случае я просто возвращаю пользователя из базы данных.
Важно, чтобы ваш supportsClass()
разрешал LdapUser и вашу собственную сущность пользователя, поскольку при первом входе в систему вы получите пользователя с сервера ldap, которым является Symfony\Component\Ldap\Security\LdapUser
.
2. Добавьте EventSubscriber в InteractiveLogin
Теперь вам нужно создать собственного пользователя, если его еще нет, затем продолжите его вход в систему с помощью собственного пользовательского объекта.
Создайте новый класс, расширяющий Symfony\Component\EventDispatcher\EventSubscriberInterface
, и используйте событие onInteractiveLogIn.
public static function getSubscribedEvents()
{
return [
SecurityEvents::INTERACTIVE_LOGIN => [
['onInteractiveLogIn', 0],
],
];
}
Внутри onInteractiveLogIn() вам нужно реализовать свой способ создания нового пользовательского объекта User, если он не найден и сохранен. Также создайте новый Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
для входа.
Мой пример:
public function onInteractiveLogIn(InteractiveLoginEvent $interactiveLoginEvent)
{
$ldapUser = $interactiveLoginEvent->getAuthenticationToken()->getUser();
if (!($ldapUser instanceof LdapUser)) {
return;
}
$localUser = // find local User with LdapUser credentials (Query);
if (!$localUser) {
// no custom user found, create own
$localUser = new User();
//...
}
// since we do not have pwd, fill it with anything to generate random token
$rmdBytes = random_bytes(32);
$localUser->setPassword($rmdBytes);
$this->entityManager->persist($localUser);
$this->entityManager->flush();
// login user with new created/already found custom user entity
$token = new UsernamePasswordToken($localUser, $rmdBytes, 'main', $localUser->getRoles());
$this->tokenStorage->setToken($token);
}
Важно: onInteractiveLogIn
уже подтверждает, что введенный pwd равен реальному pwd ldap.
3. Реализовать собственный способ обновления пользователя
Поскольку Symfony вызывает при каждом новом запросе метод refreshUser() для предоставления всегда свежего объекта данных, нам нужно переопределить поведение по умолчанию.
Ваша User
Организация должна реализовать Symfony\Component\Security\Core\User\EquatableInterface
.
Там вы можете изменить способ сравнения при вызове refreshUser()
нового обновленного пользователя и исходного пользователя. По умолчанию используется пароль, но, поскольку у нас нет пароля, мы не хотим такого поведения.
В этом примере я сравниваю UserIdentifieres, поскольку они уникальны:
public function isEqualTo(UserInterface $user)
{
return $this->getUserIdentifier() === $user->getUserIdentifier();
}
Теперь у вас должна быть возможность войти в систему без сохранения pwd в вашей собственной базе данных.
Новая система будет вызывать при каждом входе в систему сервер Ldap и запрашивать пользователя с заданными учетными данными. Если затем войти в систему, он больше не будет вызывать сервер, вместо этого автоматически обновит пользователя, если он соответствует isEqualTo()
.