Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

参考にしたのは、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としては存在しないので、使うのは避けたほうが良いかも。
そもそもセッションを直接扱うとか、このあたりの処理はハックな感じがします。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした