LoginSignup
4
6

More than 1 year has passed since last update.

Symfony認証でパスワード無しでログインする方法

Last updated at Posted at 2017-02-27

脆弱性の話のようなタイトルですが、例えば管理とユーザーのページが別々に存在していて、運用のために管理画面から任意のユーザーの画面にアクセスしたい場合です。ユーザーのパスワードはハッシュ化されてわからないので、パスワードなしでログインすることになります。

【追記2022.03.28】ものすごく簡単に実装できるようになってました。

参考にしたのは、SymfonyのGitHubでの「Sharing security context across multiple firewalls #11836」というIssueです。またSymfony3.2での動作を確認してます。

security.yml

次のようなsecurity.ymlがあったとします。Guardを使って認証してますが、おそらくそこは関係ないはず。

AppBundle:User\Userがユーザーのエンティティを表しているとします。
管理者はadminにログインした後、任意のユーザーにログインします。それにはuserというファイヤウォールを突破する必要があります。

security:
    providers:
        user_db_provider:
            entity:
                class: AppBundle:User\User
                property: mail
    firewalls:
        admin:
            pattern: ^/admin/
            # ... 省略
        user:
            context: user    # <- これがログインするコンテクスト
            pattern: ^/user/
            anonymous: ~
            guard:
                authenticators:
                    - app.tanto_authenticator
    access_control:
        - { path: ^/user/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/user/, roles: ROLE_USER}

ポイントはcontext:で指定している文字列(user)です。

Controllerのコード

管理者用コントローラーのサンプルコードです。

loginActionを使ってuser_idのユーザーにログインします。最初にユーザーのエンティティを取得してから、loginToContextでログインできます。

    /**
     * @Config\Route("/admin/user/{user_id}/login", name="admin-user-login")
     * @param int     $user_id
     * @return Response
     */
    public function loginAction($user_id)
    {
        $user = $this->getDoctrine()->getManager()
             ->getRepository(User::class)->find($user_id);
        if (!$user) {
            return $this->redirectToUserList('ユーザーを読み込めません');
        }
        $this->loginToContext($user, 'user');
        return $this->redirectToRoute('user-top'); // ユーザー画面にリダイレクト
    }
    /**
     * @param UserInterface $user
     * @param string        $context
     * @param array         $roles
     */
    private function loginToContext(UserInterface $user, $context, $roles = [])
    {
        $roles   = $roles ?: $user->getRoles();
        $token   = new UsernamePasswordToken($user, null, $context, $roles);
        $session = $this->get('session');
        $session->set('_security_'.$context, serialize($token));
        $session->save();
    }

loginToContextメソッドをそのまま解説すると、

  • UsernamePasswordTokenオブジェクトを作成して、
  • _security_userというセッション名でオブジェクトを保存すると
  • userという名前のcontextにログインできます。

この方法の注意点としては、現在ログイン中の$contextを変更することはできません。あくまでadminにログインしている状態で、userにログインする方法です。

現在のコンテクストを更新する

※番外編

例えば、アクティベーションなどの処理でユーザーのロールを変更する場合です。現在のSecurity.contextで、トークンを再登録することになります。

    /**
     * @param UserInterface $user
     * @param string        $context
     * @param array         $roles
     */
    private function updateContext(UserInterface $user, $context, $roles = [])
    {
        $roles   = $roles ?: $user->getRoles();
        $token   = new UsernamePasswordToken($user, null, $context, $roles);
        $storage = $this->get('security.token_storage');
        $storage->getToken()->setAuthenticated(false);
        $storage->setToken($token);
    }

要は、setAuthenticated(false)として一旦トークンを無効にしてから、setToken($token) で再設定します。先の直接セッションを設定する方法だと、その後のタイミングで元のトークンに戻ってしまうみたいです。

Firewall Context

ところでcontextって何でしょうね?

Symfonyのドキュメントだと「SecurityBundle Configuration ("security"): Firewall Context」というのがあります。

次のようにcontextは省略が可能で、指定されていない場合はファイヤウォール名(この場合はuser)となるとのこと。

security:
    firewalls:
        user:    # コンテキストがないので、この名前が使われる。
            pattern: ^/user/
            anonymous: ~
            guard:
                authenticators:
                    - app.tanto_authenticator

でも結局コンテクストが何かはわからないですね。ログイン情報をセッションに保存するときの名前としか。でもGuardとContextで違うエンティティを使ったりしたら、どんな挙動になるんでしょうね。

ちなみにトークンからコンテクストを取得するには、getProviderKey()が使えます。

    $storage = $this->get('security.token_storage');
    $context = $storage->getToken()->getProviderKey();

がAPIとしては存在しないので、使うのは避けたほうが良いかも。
そもそもセッションを直接扱うとか、このあたりの処理はハックな感じがします。

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6