Symfony認証でパスワード無しでログインする方法
脆弱性の話のようなタイトルですが、例えば管理とユーザーのページが別々に存在していて、運用のために管理画面から任意のユーザーの画面にアクセスしたい場合です。ユーザーのパスワードはハッシュ化されてわからないので、パスワードなしでログインすることになります。
参考にしたのは、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としては存在しないので、使うのは避けたほうが良いかも。
そもそもセッションを直接扱うとか、このあたりの処理はハックな感じがします。
Author And Source
この問題について(Symfony認証でパスワード無しでログインする方法), 我々は、より多くの情報をここで見つけました https://qiita.com/asaokamei/items/2e748b62eaa083d06d24著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .