Я использую Symfony 5.1 и пытаюсь реализовать аутентификацию LDAP, в то время как свойства пользователя (имя пользователя, роли и т. Д. c.) Хранятся в MySQL БД. Таким образом, я добавил пользовательскую сущность для Doctrine и настроил services.yml и security.yml в соответствии с документацией.
Я также использовал пакет Maker для создания LoginFormAuthenticator
, который, похоже, использует аутентификатор Guard. Модуль.
Когда я пытаюсь войти в систему, похоже, что он не делает ничего, связанного с LDAP. Я также прослушивал пакеты TCP с помощью tcpdump и не видел трафика c на сервер LDAP.
Вот мой код:
services.yml
:
services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
Symfony\Component\Ldap\Ldap:
arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
arguments:
- host: <ldap-IP>
port: 389
options:
protocol_version: 3
referrals: false
security.yml
:
security:
encoders:
App\Entity\User:
algorithm: auto
app_user_provider:
entity:
class: App\Entity\User
property: email
my_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: "<base_dn>"
search_dn: "<search_dn>"
search_password: "<password>"
default_roles: ROLE_USER
uid_key: sAMAccountName
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
provider: my_ldap
form_login_ldap:
login_path: login
check_path: login
service: Symfony\Component\Ldap\Ldap
dn_string: 'uid={username},OU=Test,DC=domain,DC=domain'
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: app_logout
# where to redirect after logout
target: index
access_control:
- { path: ^/$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/profile, roles: ROLE_USER }
LoginFormAuthenticator
, думаю, проблема здесь в функции checkCredentials
. Я нашел класс LdapBindAuthenticationProvider
, целью которого, похоже, является именно такая проверка учетных данных пользователя по сравнению с LDAP, но я совершенно не уверен, как мне это делать:
<?php
namespace App\Security;
use Psr\Log\LoggerInterface;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
use Symfony\Component\Ldap\Ldap;
use Symfony\Component\Security\Core\Authentication\Provider\LdapBindAuthenticationProvider;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
private $logger;
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
private $ldap;
public function __construct(LoggerInterface $logger, EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder, Ldap $ldap, LdapBindAuthenticationProvider $form_login_ldap)
{
$this->logger = $logger;
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
$this->ldap = $ldap;
}
public function supports(Request $request): ?bool
{
return self::LOGIN_ROUTE === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'username' => $request->request->get('username'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['username']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['username']]);
if (!$user) {
// user not found in db, but may exist in ldap:
$user = $userProvider->loadUserByUsername($credentials['username']);
if (!$user) {
// user simply doesn't exist
throw new CustomUserMessageAuthenticationException('Email could not be found.');
} else {
// user never logged in before, create user in DB and proceed...
// TODO
}
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
// TODO: how to use the LdapBindAuthenticationProvider here to check the users credentials agains LDAP?
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function getPassword($credentials): ?string
{
return $credentials['password'];
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
// For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
К сожалению, я не нашел ни одного примера код для этого.
ОБНОВЛЕНИЕ:
Благодаря ответу Т. ван ден Берга мне, наконец, удалось заставить работать аутентификационную часть. Я удалил LoginFormAuthenticator Guard из security.yml и немного изменил form_login_ldap. его имя входа (sAMAccountName), а затем во втором запросе использовать отличительное имя (DN) этого объекта LDAP, чтобы выполнить еще одну аутентификацию на сервере LDAP с предоставленным паролем. Пока все в порядке.
Единственное, чего не хватает, - это сохраненный в базе данных объект User. Моя идея заключалась в следующем:
- отправлена форма входа
- поиск в базе данных сущности пользователя с предоставленным именем пользователя
- , если сущность пользователя не найдена используйте LDAPUserProvider, чтобы запросить у LDAP имя пользователя
- , если Пользователь существует в LDAP, создайте объект User в базе данных
- аутентифицируйте пользователя в LDAP с помощью предоставленного пароля
Пароль не сохраняется в базе данных, но информация о другом приложении c недоступна в LDAP (например, последнее действие).
Кто-нибудь знает, как это реализовать?