web-dev-qa-db-ja.com

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

Symfony 2でゼロからビジネスアプリを構築していますが、ユーザー登録フローでちょっとした障害に遭遇しました:ユーザーがアカウントを作成した後、それらの資格情報で自動的にログインする必要がありますすぐに再び資格情報を提供することを強制されます。

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

108
Problematic

Symfony 4.0

このプロセスはsymfony 3から4に変更されていませんが、ここでは新しく推奨されるAbstractControllerを使用した例を示します。 security.token_storageサービスとsessionサービスの両方が親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.contextsecurity.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になります

140
Chase

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

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

RegistrationController.php:

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

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

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

65
Problematic

私はSymfony 2.2を使用していますが、私の経験は Problematicの とは少し異なります。したがって、これはこの質問のすべての情報と自分の情報を組み合わせたものです。

Joe は、UsernamePasswordTokenコンストラクターの3番目のパラメーターである_$providerKey_の値について間違っていると思います。これは、認証(ユーザーではなく)プロバイダーのキーになるはずです。認証システムによって、異なるプロバイダー用に作成されたトークンを区別するために使用されます。 UserAuthenticationProvider から派生したプロバイダーは、プロバイダーキーが独自のものと一致するトークンのみを認証します。たとえば、 UsernamePasswordFormAuthenticationListener は、作成するトークンのキーを、対応する DaoAuthenticationProvider 。これにより、1つのファイアウォールに複数のユーザー名+パスワードプロバイダーを設定できます。したがって、他のプロバイダーと競合しないキーを選択する必要があります。 _'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をハイジャックするのではなく、独自のトークンクラスで新しい認証プロバイダーを実装する 方が正しいでしょう 。また、適切なプロバイダーを使用すると、イベントが自動的に処理されます。

6
Sam Hanes

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

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

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

呼び出し中

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

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

つまりファイアウォールの制御内のURLからのみユーザーをログインできます。

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

4
daemonl

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

firewalls:
    foobar:
        pattern:    /foo/
2
Nim

ここですべての答えを試しましたが、どれも機能しませんでした。コントローラでユーザーを認証できる唯一の方法は、サブリクエストを作成してからリダイレクトすることです。ここに私のコードがあります、私は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'));
2
Diego Castro

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")

1
Nico