1

Apache の auth_kerb を使用して認証するアプリケーションで switch_user 機能をセットアップしようとしています。REMOTE_USER正しく返され、ログインできます。ただし、別のユーザーになりすまそうとするとできません。切り替え先のユーザーはアプリケーション内に存在します。ユーザーを切り替えようとする試みが発生しますが、事前認証が再度呼び出され、イニシャルREMOTE_USERが読み込まれます。

remote_user とカスタム ユーザー プロバイダーを使用して switch_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';
  }

}
4

1 に答える 1

0

この修正には、AbstractPreAuthenticatedListener を適応させて、ログイン ユーザーに一致する標準トークンの存在を確認することが含まれます。そうでない場合は、ログイン ユーザーを保存しているが、「切り替え先」のユーザー ID に関連付けられているカスタマイズされたトークンが存在するかどうかを確認します。

これは、コードの重要な (コピーされていない) 部分です。

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.
 */

public function __construct($user, $credentials, $providerKey, array $roles = array(), $preAuthenticatedData) {parent::__construct($roles);

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: ユーザー データのクエリ。
于 2015-06-25T06:16:38.753 に答える