Невозможно выдать себя за пользователя при аутентификации с использованием слушателя 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: запросы для пользовательских данных.
Другие вопросы по тегам