Я пытаюсь реализовать простую аутентификацию LDAP в своем приложении Symfony.
Пользователь должен сначала пройти аутентификацию в LDAP, при этом из базы данных должна быть возвращена пользовательская сущность пользователя.
Если пользователя нет в базе данных, но он может быть успешно аутентифицирован, я хочу создать пользователя.
За исключением автоматического создания пользователя в базе, пока работает.
providers:
users_db:
entity:
# the class of the entity that represents users
class: 'App\Entity\User'
# the property to query by - e.g. email, username, etc
property: 'username'
users_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: '%env(LDAP_BASE_DN)%'
search_dn: '%env(LDAP_SEARCH_DN)%'
search_password: '%env(LDAP_SEARCH_PASSWORD)%'
default_roles: ROLE_USER
uid_key: uid
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users_db
http_basic_ldap:
service: Symfony\Component\Ldap\Ldap
dn_string: '%env(LDAP_DN_STRING)%'
С приведенной выше конфигурацией аутентификация работает с LDAP, но пользователь приходит из БД. Поэтому, если я не создал соответствующего пользователя, попытка входа не сработает.
Я пробовал автоматическое создание пользователей с помощью UserChecker , UserProvider и LoginEventListener (прослушивает событие onAuthenticationSuccess
), к сожалению, безуспешно.
Событие onAuthenticationSuccess вызывается только после успешной аутентификации и поэтому не может использоваться в конфигурации, описанной выше, поскольку провайдер users_db (пока) не содержит пользователя, даже если базовая аутентификация LDAP работает.
Затем я попробовал это с помощью UserChecker и сетевого провайдера [users_ldap, users_db]
. Это также выполняется, но тогда я получаю уже не пользовательский объект, а пользователя LDAP. Поэтому я попытался создать свой собственный UserProvider, но, к сожалению, безуспешно.
Если кто-нибудь знает хороший способ сделать это, я был бы признателен за ответ или короткий комментарий. Спасибо!
Большое спасибо за предложение. Я сейчас сделал это аналогично https://stackoverflow.com/a/72138096/2117102. Это было немного сложно, но я думаю, что его подход имеет смысл. Я приложу ответ с кодом, как только найду время.
Я сталкивался с вашей проблемой раньше, мне не удалось автоматически сохранить их информацию в моей локальной базе данных, поэтому я выполняю следующие шаги:
<div>
{{ form_row(user_form.uid, { label: 'UID :*',
required: 'true',
attr: {
value : app.user.uid,
readonly: true
}
}) }}
</div>
<div class = "invisible" >
{{ form_row(user_form.genre,{
attr: { value : app.user.supannCivilite }
}) }}
</div>
Я использую этот пакет для LDAP, поэтому, пожалуйста, дайте мне знать, если вам нужна помощь!
Кроме того, вы можете взглянуть на мою конфигурацию безопасности, как показано ниже, надеюсь, что это будет полезно:
providers:
chain_provider:
chain:
providers: [in_memory, database, ldap]
in_memory:
memory:
users:
__NO_USER__:
password:
roles: ROLE_ANON
database:
entity:
class: App\Entity\User
property: uid
ldap:
id: ldap_user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
l3_firewall:
pattern: ^/
provider: chain_provider
security: true
guard:
authenticators:
- cas.security.authentication.authenticator
logout:
path: /logout
success_handler: authentication_handler
invalidate_session: false
access_denied_handler: App\EventListener\AccessDeniedListener
main:
pattern: ^/
security: true
lazy: true
provider: chain_provider
guard:
authenticators:
- cas.security.authentication.authenticator
Большое спасибо за предложение и пример кода. Однако сейчас я решил это аналогично этому: https://stackoverflow.com/a/72138096/2117102.
Благодаря этому ответу теперь у меня все работает.
конфиг/services.yaml
services:
App\Security\UserProvider:
arguments:
$em: '@Doctrine\ORM\EntityManagerInterface'
$ldap: '@Symfony\Component\Ldap\Ldap'
$baseDn: "%env(LDAP_BASE_DN)%"
$searchDn: "%env(LDAP_SEARCH_DN)%"
$searchPassword: "%env(LDAP_SEARCH_PASSWORD)%"
$defaultRoles: ["ROLE_USER"]
$uidKey: "uid"
$extraFields: []
App\EventListener\LoginListener:
arguments:
- "@doctrine.orm.entity_manager"
конфигурация/пакеты/security.yml
security:
enable_authenticator_manager: true
password_hashers:
App\Entity\User: 'auto'
providers:
users:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users
stateless: false
http_basic_ldap:
service: Symfony\Component\Ldap\Ldap
dn_string: 'uid = {username},ou=accounts,dc=example,dc=com'
src/Безопасность/UserProvider.php
<?php
namespace App\Security;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Ldap\Security\LdapUserProvider;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Ldap\Ldap;
use Symfony\Component\Ldap\Security\LdapUser;
class UserProvider extends LdapUserProvider
{
private $em;
public function __construct(EntityManagerInterface $em, Ldap $ldap, string $baseDn, string $searchDn = null, string $searchPassword = null, array $defaultRoles = [], string $uidKey = null, string $filter = null, string $passwordAttribute = null, array $extraFields = [])
{
parent::__construct($ldap, $baseDn, $searchDn, $searchPassword, $defaultRoles, $uidKey, $filter, $passwordAttribute, $extraFields);
$this->em = $em;
}
/**
* Refreshes the user after being reloaded from the session.
*
* @return UserInterface
*/
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
$refreshUser = $this->em->getRepository(User::class)->findOneBy(['username' => $user->getUserIdentifier()]);
return $refreshUser;
}
/**
* Tells Symfony to use this provider for this User class.
*/
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);
}
}
src/EventListener/LoginListener.php
<?php
namespace App\EventListener;
use App\Entity\User;
use Doctrine\ORM\EntityManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Ldap\Security\LdapUser;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class LoginListener implements EventSubscriberInterface
{
private $em;
private $tokenStorage;
function __construct(EntityManager $em, TokenStorageInterface $tokenStorage)
{
$this->em = $em;
$this->tokenStorage = $tokenStorage;
}
public static function getSubscribedEvents(): array
{
return [LoginSuccessEvent::class => 'onLoginSuccess'];
}
public function onLoginSuccess(LoginSuccessEvent $loginSuccessEvent)
{
$ldapUser = $loginSuccessEvent->getAuthenticatedToken()->getUser();
if (!($ldapUser instanceof LdapUser)) {
return;
}
$localUser = $this->em->getRepository(User::class)->findOneBy(['username' => $ldapUser->getUserIdentifier()]);
if (!$localUser) {
// No local user found in database -> create new user
$localUser = new User();
$localUser->setUsername($ldapUser->getUserIdentifier());
}
// We don't store user passwords -> generate random token
$rmdBytes = random_bytes(32);
$localUser->setPassword($rmdBytes);
$this->em->persist($localUser);
$this->em->flush();
// Login user
$token = new UsernamePasswordToken($localUser, $rmdBytes, 'main', $localUser->getRoles());
$this->tokenStorage->setToken($token);
}
}
Я также реализовал EquatableInterface
в объекте User
, как это было предложено в упомянутом stackoverflow.
у меня был аналогичный проект, где мне не нужно было хранить пароли пользователей, только имя пользователя, адрес электронной почты и имя (данные, полученные из ldap, когда аутентификация прошла успешно). Используется пользовательский аутентификатор, где, когда пользователь отправляет форму (с именем пользователя и паролем), я проверяю их на сервере ldap, в случае успеха проверяю мою базу данных, если имя пользователя (из формы) существует, если не создается новый объект с данные из ответа ldap.