登録後の自動ユーザー認証


114

私たちはSymfony 2でゼロからビジネスアプリを構築しており、ユーザー登録フローに少し問題がありました。ユーザーがアカウントを作成した後、代わりにそれらの資格情報で自動的にログインする必要がありますすぐに再び資格情報を提供する必要があります。

誰もがこれについて何か経験がありましたか、または私を正しい方向に向けることができましたか?

回答:


146

symfony 4.0

このプロセスはsymfony 3から4に変更されていませんが、新しく推奨されたAbstractControllerを使用した例を次に示します。security.token_storagesessionサービスの両方が親getSubscribedServicesメソッドに登録されているので、それらをコントローラーに追加する必要はありません。

use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use YourNameSpace\UserBundle\Entity\User;

class LoginController extends AbstractController{

    public function registerAction()
    {    
        $user = //Handle getting or creating the user entity likely with a posted form
        $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
        $this->container->get('security.token_storage')->setToken($token);
        $this->container->get('session')->set('_security_main', serialize($token));
        // The user is now logged in, you can redirect or do whatever.
    }

}

Symfony 2.6.x-Symfony 3.0.x

symfony 2.6の時点でsecurity.context非推奨になりましたsecurity.token_storage。これでコントローラーは次のようになります。

use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use YourNameSpace\UserBundle\Entity\User;

class LoginController extends Controller{

    public function registerAction()
    {    
        $user = //Handle getting or creating the user entity likely with a posted form
        $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
        $this->get('security.token_storage')->setToken($token);
        $this->get('session')->set('_security_main', serialize($token));
    }

}

これは非推奨ですがsecurity.context、下位互換性が確保されているため、引き続き使用できます。Symfony 3用に更新する準備をしてください

セキュリティのための2.6の変更について詳しくは、https//github.com/symfony/symfony/blob/2.6/UPGRADE-2.6.mdを参照してください。

symfony 2.3.x

symfony 2.3でこれを達成するために、もはやセキュリティコンテキストでトークンを設定することはできません。トークンをセッションに保存する必要もあります。

次のようなファイアウォールを備えたセキュリティファイルを想定します。

// app/config/security.yml
security:
    firewalls:
        main:
            //firewall settings here

そして、同様のコントローラーアクション:

use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use YourNameSpace\UserBundle\Entity\User;

class LoginController extends Controller{

    public function registerAction()
    {    
        $user = //Handle getting or creating the user entity likely with a posted form
        $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
        $this->get('security.context')->setToken($token);
        $this->get('session')->set('_security_main',serialize($token));
        //Now you can redirect where ever you need and the user will be logged in
    }

}

トークンを作成するにはUsernamePasswordToken、を作成します。これは、4つのパラメーターを受け入れます:ユーザーエンティティ、ユーザー資格情報、ファイアウォール名、ユーザーロール。トークンを有効にするためにユーザー資格情報を提供する必要はありません。

security.contextすぐにリダイレクトする場合は、トークンをに設定する必要があることを100%確信していません。しかし、それは痛くないようですので、私はそれを残しました。

次に重要な部分は、セッション変数を設定することです。変数の命名規則の_security_後にファイアウォール名が続きます。この場合main_security_main


1
コードを実装しましたが、ユーザーは正常にログに記録しましたが、$ this-> getUser()オブジェクトはNULLを返します。何か案が?
熱心な

2
クレイジーなことがなしに起こっていました$this->get('session')->set('_security_main', serialize($token));。@Chausserありがとうございます!
Dmitriy

1
名前付きファイアウォールのためのトークンを設定した場合のSymfony 2.6ではmain、あなたが名前の別のファイアウォールで認証されているadmin(あなたがユーザーを偽装しているとして)、奇妙なことが起こります:_security_admin取得UsernamePasswordTokenあなたが提供されたユーザーで、つまりあなたがから「切断」を取得しますあなたのadminファイアウォール。「admin」ファイアウォールのトークンを維持する方法はありますか?
グレモ2015年

1
正直に言うと、2つのファイアウォールで同時に認証できるかどうかはわかりませんが、よく調べてください。その間、別の質問をする必要があります
Chase

3
@Chausserはそれを機能させることができました。答えは完全に正しい(そして更新されている)、同じターゲットファイアウォールのsetToken(..)下で、またはまだ認証されてない状態で呼び出した場合にのみ機能します。
グレモ2015年

65

最後にこれを考え出した。

ユーザー登録後、プロバイダー設定でユーザーエンティティとして設定したもののオブジェクトインスタンスにアクセスできるはずです。解決策は、そのユーザーエンティティで新しいトークンを作成し、それをセキュリティコンテキストに渡すことです。これは私のセットアップに基づく例です:

RegistrationController.php:

$token = new UsernamePasswordToken($userEntity, null, 'main', array('ROLE_USER'));
$this->get('security.context')->setToken($token);

mainアプリケーションのファイアウォールの名前はどこですか(ありがとう、@ Joe)。それだけです。システムは、完全にログインしたユーザーを、作成したばかりのユーザーと見なします。

編集:@Miquelのコメントに従って、コントローラーコードサンプルを更新して、新しいユーザーに適切なデフォルトの役割を含めました(ただし、これはアプリケーションの特定のニーズに応じて調整できます)。


2
これはSymfony 2のリリースバージョンでは正しくありません。ユーザー名のロールをUsernamePasswordTokenコンストラクターの4番目の引数として渡す必要があります。そうしないと、認証されていないとマークされ、ユーザーにはロールがありません。
マイケル

「Remember me」フラグについてはどうですか?ユーザーを手動でログインする方法だけでなく、永久にログインする必要があります。このコードはその問題を解決しません。
maectpo 2012年

@maectpoは元の要件の範囲外でしたが、すばらしいフォローアップの回答のように聞こえます。思いついたことを教えてください。
問題のある2012年

問題があります。この方法でログインできますが、app.user変数は空です。このログインプロセスでこの変数を設定する方法を知っていますか?: -私は、ユーザー(文字列)とパスワード(文字列)を送るように参照言うapi.symfony.com/2.0/Symfony/Component/Security/Core/...
unairoldan

1
マルクは、以下の言ったように、あなたはUsernamePasswordToken名前空間を登録する必要があります:use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
MrGlass

6

UserInterfaceオブジェクトがある場合(ほとんどの場合そうなります)、最後の引数に実装するgetRoles関数を使用することをお勧めします。したがって、関数logUserを作成すると、次のようになります。

public function logUser(UserInterface $user) {
    $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
    $this->container->get('security.context')->setToken($token);
}

6

私はSymfony 2.2を使用していて、私の経験はProblematicのそれとは少し異なっていたので、これはこの質問のすべての情報といくつかの情報を組み合わせたバージョンです。

Joe$providerKeyUsernamePasswordTokenコンストラクタの3番目のパラメータであるの値について間違っていると思います。これは、(ユーザーではなく)認証プロバイダーのキーとなるはずです。認証システムは、さまざまなプロバイダー用に作成されたトークンを区別するために使用されます。子孫のプロバイダーはUserAuthenticationProvider、プロバイダーキーが自身のトークンと一致するトークンのみを認証します。たとえば、はUsernamePasswordFormAuthenticationListener作成するトークンのキーを、対応するのキーと一致するように設定しますDaoAuthenticationProvider。これにより、単一のファイアウォールが複数のユーザー名+パスワードプロバイダーを使用して、それらが互いに踏み込むことがなくなります。したがって、他のプロバイダーと競合しないキーを選択する必要があります。使用します'new_user'

アプリケーションの他の部分に、認証成功イベントに依存するシステムがいくつかあります。これは、コンテキストにトークンを設定するだけでは起動されません。EventDispatcherコンテナーからを取得し、イベントを手動で起動する必要がありました。明示的なログインリクエストに応答するのではなく、暗黙的にユーザーを認証しているため、インタラクティブログインイベントも発生させないことにしました。

use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\Security\Core\Event\AuthenticationEvent;

$user = // get a Symfony user instance somehow
$token = new UsernamePasswordToken(
        $user, null, 'new_user', $user->getRoles() );
$this->get( 'security.context' )->setToken( $token );
$this->get( 'event_dispatcher' )->dispatch(
        AuthenticationEvents::AUTHENTICATION_SUCCESS,
        new AuthenticationEvent( $token ) );

の使用で$this->get( .. )は、スニペットがコントローラーメソッド内にあると想定しています。別の場所でコードを使用ContainerInterface::get( ... )している場合は、環境に適した方法で呼び出すようにコードを変更する必要があります。たまたま私のユーザーエンティティは実装されているUserInterfaceので、トークンで直接使用できます。そうでない場合は、UserInterfaceインスタンスに変換する方法を見つける必要があります。

そのコードは機能しますが、Symfonyの認証アーキテクチャを操作するのではなく、ハッキングしているように感じます。をハイジャックするよりも、独自のトークンクラスを使用して新しい認証プロバイダー実装する方がおそらく正しいでしょうUsernamePasswordToken。また、適切なプロバイダーを使用すると、イベントが処理されます。


4

誰かが私にここに戻ってきた同じ後続の質問がある場合:

呼び出す

$this->container->get('security.context')->setToken($token); 

security.context使用されるルートの電流にのみ影響します。

つまり、ファイアウォールの制御下にあるURLからのみユーザーにログインできます。

(必要に応じて、ルートの例外を追加します- IS_AUTHENTICATED_ANONYMOUSLY


1
セッションでこれを行う方法を知っていますか?現在のリクエストだけではなく、
ジェイクN

3

Symfony 4.4では、コントローラーメソッドで次のことを簡単に実行できます(Symfonyのドキュメントから参照:https : //symfony.com/doc/current/security/guard_authentication.html#manually-authenticating-a-user):

// src/Controller/RegistrationController.php
// ...

use App\Security\LoginFormAuthenticator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;

class RegistrationController extends AbstractController
{
    public function register(LoginFormAuthenticator $authenticator, GuardAuthenticatorHandler $guardHandler, Request $request)
    {
        // ...

        // after validating the user and saving them to the database
        // authenticate the user and use onAuthenticationSuccess on the authenticator
        return $guardHandler->authenticateUserAndHandleSuccess(
            $user,          // the User object you just created
            $request,
            $authenticator, // authenticator whose onAuthenticationSuccess you want to use
            'main'          // the name of your firewall in security.yaml
        );
    }
}

重要なことの1つは、ファイアウォールがに設定されていないことを確認してくださいlazy。その場合、トークンはセッションに保存されず、ログインすることもありません。

firewalls:
    main:
        anonymous: ~ # this and not 'lazy'

2

すでにここで問題となっているように、このわかりにくい$ providerKeyパラメータは実際にはファイアウォールルールの名前にすぎません。以下の例の場合は「foobar」です。

firewalls:
    foobar:
        pattern:    /foo/

たとえば、任意の文字列をblablabla3番目のパラメータとしてUsernamePasswordToken に渡しても機能する理由を教えてください。このパラメータの意味は何ですか?
ミハイル、

1
このパラメーターは、トークンを特定のファイアウォールプロバイダーにバインドします。ほとんどの場合、プロバイダーは1つだけなので、気にする必要はありません。
Gottlieb Notschnabel 2015

2

ここですべての答えを試しましたが、どれもうまくいきませんでした。コントローラでユーザーを認証できる唯一の方法は、サブリクエストを作成してからリダイレクトすることです。これが私のコードです、私はsilexを使用していますが、symfony2に簡単に適応できます:

$subRequest = Request::create($app['url_generator']->generate('login_check'), 'POST', array('_username' => $email, '_password' => $password, $request->cookies->all(), array(), $request->server->all());

$response = $app->handle($subRequest, HttpKernelInterface::MASTER_REQUEST, false);

return $app->redirect($app['url_generator']->generate('curriculos.editar'));

1

Symfonyバージョン2.8.11(おそらく古いバージョンと新しいバージョンで動作します)で、FOSUserBundleを使用する場合は、次のようにします

try {
    $this->container->get('fos_user.security.login_manager')->loginUser(
    $this->container->getParameter('fos_user.firewall_name'), $user, null);
} catch (AccountStatusException $ex) {
    // We simply do not authenticate users which do not pass the user
    // checker (not enabled, expired, etc.).
}

他のソリューションで見たように、イベントをディスパッチする必要はありません。

FOS \ UserBundle \ Controller \ RegistrationController :: authenticateUserから作成

(composer.json FOSUserBundleバージョンから: "friendsofsymfony / user-bundle": "〜1.3")

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.