Невозможно выдать себя за пользователя при аутентификации с использованием слушателя auth_kerb
Я пытаюсь настроить функциональность switch_user в приложении, которое аутентифицируется с использованием auth_kerb Apache. REMOTE_USER
возвращается правильно и я могу войти в систему. Однако, когда я пытаюсь маскироваться под другого пользователя, я не могу. Пользователь, на которого я хочу переключиться, существует в приложении. Попытка переключить пользователя происходит, но предварительная проверка подлинности вызывается снова и REMOTE_USER
загружен.
Любые идеи о том, как заставить switch_user работать с использованием remote_user и пользовательского поставщика?
security.yml
security:
providers:
webservice_user_provider:
id: webservice_user_provider
...
firewalls:
secured_area:
switch_user: { role: ROLE_ALLOWED_TO_SWITCH, parameter: _masquerade }
pattern: ^/
remote_user:
provider: webservice_user_provider
...
services.yml
parameters:
account.security_listener.class: Acme\MyUserBundle\Listener\SecurityListener
services:
account.security_listener:
class: %account.security_listener.class%
arguments: ['@security.authorization_checker', '@session', '@doctrine.orm.entity_manager', '@router', '@event_dispatcher']
tags:
- { name: kernel.event_listener, event: security.authentication.failure, method: onAuthenticationFailure }
- { name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin }
- { name: kernel.event_listener, event: security.switch_user, method: onSecuritySwitchUser }
webservice_user_provider:
class: Acme\MyUserBundle\Security\User\WebserviceUserProvider
calls:
- [setEntityManager , ['@logger', '@doctrine.orm.entity_manager']]
...
SecurityListener.php
namespace Acme\MyUserBundle\Listener;
use ...
/**
* Class SecurityListener
* @package Acme\MyUserBundle\Listener
*/
class SecurityListener {
protected $session;
protected $security;
protected $em;
protected $router;
protected $dispatcher;
public function __construct(
AuthorizationCheckerInterface $security,
Session $session,
EntityManager $em,
UrlGeneratorInterface $router,
EventDispatcherInterface $dispatcher
// TraceableEventDispatcher $dispatcher
// ContainerAwareEventDispatcher $dispatcher
) {
$this->security = $security;
$this->session = $session;
$this->em = $em;
$this->router = $router;
$this->dispatcher = $dispatcher;
}
/**
*
* @param AuthenticationFailureEvent $event
* @throws AuthenticationException
*/
public function onAuthenticationFailure(AuthenticationFailureEvent $event) {
throw new AuthenticationException($event->getAuthenticationException());
}
/**
* @param InteractiveLoginEvent $event
*/
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event) {
// set some defaults...
}
/**
* @param SwitchUserEvent $event
*/
public function onSecuritySwitchUser(SwitchUserEvent $event) {
$this->dispatcher->addListener(KernelEvents::RESPONSE, array($this, 'onSwitchUserResponse'));
}
/**
* @param FilterResponseEvent $event
*/
public function onSwitchUserResponse(FilterResponseEvent $event) {
$response = new RedirectResponse($this->router->generate('acme_mybundle_default_index'));
$event->setResponse($response);
}
}
WebServiceProvider.php
namespace Acme\MyUserBundle\Security\User;
use ...
class WebserviceUserProvider implements UserProviderInterface {
protected $entityManager;
protected $logger;
/**
*
* @param LoggerInterface $logger
* @param EntityManager $em
*/
public function setEntityManager(LoggerInterface $logger, EntityManager $em) {
$this->logger = $logger;
$this->entityManager = $em;
}
/**
*
* @param string $username
* @return Person
* @throws UsernameNotFoundException
*/
public function loadUserByUsername($username) {
# Find the person
$person = $this->entityManager->getRepository('AcmeMyBundle:Person')
->find($username);
if ($person) {
$this->logger->debug("Logged in, finding person: " . $person->getUsername());
return $person;
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
/**
*
* @param \Symfony\Component\Security\Core\User\UserInterface $person
* @throws \Symfony\Component\Security\Core\Exception\UnsupportedUserException
* @internal param \Symfony\Component\Security\Core\User\UserInterface $user
* @return Person
*/
public function refreshUser(UserInterface $person) {
if (!$person instanceof Person) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($person))
);
}
return $this->loadUserByUsername($person->getUsername());
}
/**
*
* @param type $class
* @return type
*/
public function supportsClass($class) {
return $class === 'Acme\MyBundle\Entity\Person';
}
}
1 ответ
Это исправление включает в себя адаптацию AbstractPreAuthenticatedListener для проверки существования стандартного токена, который соответствует вошедшему в систему пользователю, и, если не настраиваемого токена, в котором сохранен вошедший в систему пользователь, но прикреплен к идентификатору пользователя "переключен на".
Это важная (не скопированная) часть кода:
if (null !== $token = $this->securityContext->getToken()) {
if ($token instanceof PreAuthenticatedToken && $this->providerKey == $token->getProviderKey() && $token->isAuthenticated() && $token->getUsername() === $user) {
return;
}
// Switch user token. Check the original token.
if ($token instanceof PreAuthenticatedSwitchUserToken && $this->providerKey == $token->getProviderKey() && $token->isAuthenticated() && $token->getOriginalUsername() === $user) {
return;
}
}
Токен хранит вошедшего в систему пользователя и возвращает его с getOriginalUsername.
Сохраните существующие данные аутентификации (переданные в $preAuthenticatedData)
/**
* Constructor.
*/
открытая функция __construct($user, $credentials, $providerKey, массив $role = array(), $preAuthenticatedData) { parent::__construct($role);
if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.');
}
$this->setUser($user);
$this->credentials = $credentials;
$this->providerKey = $providerKey;
if (!is_array($preAuthenticatedData) && count($preAuthenticatedData) > 0) {
throw new \InvalidArgumentException('No preauthenticated data. Must have the server login credentials.');
}
$this->original_username = $preAuthenticatedData[0];
if ($roles) {
$this->setAuthenticated(true);
}
}
добытчик
public function getOriginalUsername() {
return $this->original_username;
}
Копить изменения
/**
* {@inheritdoc}
*/
public function serialize()
{
return serialize(array($this->credentials, $this->providerKey, $this->original_username, parent::serialize()));
}
/**
* {@inheritdoc}
*/
public function unserialize($str)
{
list($this->credentials, $this->providerKey, $this->original_username, $parentStr) = unserialize($str);
parent::unserialize($parentStr);
}
Эти изменения вписываются в контекст более широкой настройки системы безопасности Symfony. Исходный код для этого находится в github.
1 services.yml
Установите account.security_listener, security.authentication.switchuser_listener и security.authentication.listener.remote_user_switch
Это в дополнение к ожидаемому пользователю провайдера.
2 security.yml
Используйте этот поставщик безопасности
secured_area:
switch_user: { role: ROLE_ALLOWED_TO_SWITCH, parameter: _masquerade }
pattern: ^/
remote_user_switch:
provider: webservice_user_provider
3 Убедитесь, что поставщик пользователя загружает данные поддержки для вашего пользователя.
4 Установите файлы безопасности.
- RemoteUserSwitchFactory.php: определяет слушателя для обработки
события аутентификации. - PreAuthenticatedWithSwitchUserListener.php: наша специальная логика аутентификации. SwitchUserListener.php: обрабатывает пользовательское событие коммутатора.
- PreAuthenticatedSwitchUserToken.php: токен для хранения вошедшего в систему пользователя в качестве вторичных данных.
- WebserviceUser.php: наш объект пользовательских данных
- WebserviceUserProvider.php: запросы для пользовательских данных.