web-dev-qa-db-fra.com

Authentification automatique des utilisateurs après l'enregistrement

Nous avons créé une application métier à partir de zéro dans Symfony 2, et le flux d'enregistrement de l'utilisateur me pose problème: une fois que l'utilisateur a créé un compte, il doit être automatiquement connecté avec ces informations d'identification d'être immédiatement obligés de fournir à nouveau leurs informations d'identification.

Quelqu'un en a-t-il déjà fait l'expérience ou est-il capable de me diriger dans la bonne direction?

106
Problematic

Symfony 4.0

Ce processus n'a pas changé de symfony 3 à 4, mais voici un exemple utilisant le contrôleur abstrait récemment recommandé. Les services security.token_storage et session sont tous deux enregistrés dans la méthode parent getSubscribedServices. Vous n'avez donc pas besoin de les ajouter dans votre contrôleur.

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

A partir de symfony 2.6, security.context est déconseillé au profit de security.token_storage. Le contrôleur peut maintenant être simplement:

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));
    }

}

Bien que cela soit déconseillé, vous pouvez toujours utiliser security.context car il a été conçu pour être rétro-compatible. Juste être prêt à le mettre à jour pour Symfony 3

Vous pouvez en savoir plus sur les modifications de la version 2.6 pour la sécurité ici: https://github.com/symfony/symfony/blob/2.6/UPGRADE-2.6.md

Symfony 2.3.x

Pour accomplir cela dans Symfony 2.3, vous ne pouvez plus simplement définir le jeton dans le contexte de sécurité. Vous devez également enregistrer le jeton dans la session.

En supposant un fichier de sécurité avec un pare-feu comme:

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

Et une action de contrôleur similaire aussi:

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
    }

}

Pour la création du jeton, vous souhaiterez créer une variable UsernamePasswordToken. Elle accepte 4 paramètres: entité utilisateur, informations d'identification utilisateur, nom du pare-feu, rôles d'utilisateur. Vous n'avez pas besoin de fournir les informations d'identification de l'utilisateur pour que le jeton soit valide.

Je ne suis pas sûr à 100% que la configuration du jeton sur le security.context soit nécessaire si vous souhaitez simplement rediriger immédiatement. Mais cela ne semble pas faire mal alors je l'ai laissé.

Ensuite, la partie importante, la définition de la variable de session. La convention de nommage des variables est _security_ suivie du nom de votre pare-feu, dans ce cas, main rendant _security_main.

138
Chase

Compris celui-ci, finalement.

Après l'enregistrement de l'utilisateur, vous devriez avoir accès à une instance d'objet de tout ce que vous avez défini comme entité d'utilisateur dans la configuration de votre fournisseur. La solution consiste à créer un nouveau jeton avec cette entité utilisateur et à le transférer dans le contexte de sécurité. Voici un exemple basé sur ma configuration:

RegistrationController.php:

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

main est le nom du pare-feu de votre application (merci, @ Joe). C'est vraiment tout ce qu'il y a à faire. le système considère maintenant que votre utilisateur est entièrement connecté comme l'utilisateur qu'il vient de créer.

EDIT: Selon le commentaire de @ Miquel, j'ai mis à jour l'exemple de code de contrôleur afin d'inclure un rôle par défaut judicieux pour un nouvel utilisateur (bien que cela puisse évidemment être ajusté en fonction des besoins spécifiques de votre application).

65
Problematic

J'utilise Symfony 2.2 et mon expérience était légèrement différente de celle de Problematic . Il s'agit donc d'une version combinée de toutes les informations de cette question et de certaines des miennes. 

Je pense que Joe a tort à propos de la valeur de $providerKey, le troisième paramètre du constructeur UsernamePasswordToken. C'est censé être la clé d'un fournisseur d'authentification (pas d'utilisateur). Il est utilisé par le système d'authentification pour distinguer les jetons créés pour différents fournisseurs. Tout fournisseur descendant de UserAuthenticationProvider authentifiera uniquement les jetons dont la clé de fournisseur correspond à la sienne. Par exemple, UsernamePasswordFormAuthenticationListener définit la clé du jeton créée pour qu'elle corresponde à celle de DaoAuthenticationProvider . Cela permet à un même pare-feu de disposer de plusieurs fournisseurs de noms d'utilisateur et de mots de passe sans qu'ils se superposent. Nous devons donc choisir une clé qui ne sera pas en conflit avec d’autres fournisseurs. J'utilise 'new_user'.

Il existe quelques systèmes dans d'autres parties de mon application qui dépendent de l'événement de réussite authentication , et cela ne se déclenche pas en définissant simplement le jeton sur le contexte. Je devais obtenir la EventDispatcher du conteneur et déclencher l'événement manuellement. J'ai également décidé de ne pas déclencher un événement de connexion interactive , car nous authentifions l'utilisateur implicitement et non en réponse à une demande de connexion explicite.

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

Notez que l'utilisation de $this->get( .. ) suppose que l'extrait de code est dans une méthode de contrôleur. Si vous utilisez le code ailleurs, vous devrez les changer pour appeler ContainerInterface::get( ... ) d'une manière appropriée à l'environnement. En l'occurrence, mes entités utilisateur implémentent UserInterface pour que je puisse les utiliser directement avec le jeton. Si ce n'est pas le cas, vous devrez trouver un moyen de les convertir en instances UserInterface.

Ce code fonctionne, mais j’ai l’impression de modifier l’architecture d’authentification de Symfony plutôt que de l’utiliser. Il serait probablement plus correct de implémenter un nouveau fournisseur d'authentification avec sa propre classe de jetons plutôt que de détourner la UsernamePasswordToken. En outre, l'utilisation d'un fournisseur approprié signifierait que les événements ont été gérés pour vous.

6
Sam Hanes

Si vous avez un objet UserInterface (et cela devrait être le cas la plupart du temps), vous pouvez utiliser la fonction getRoles qu'il implémente pour le dernier argument . Donc, si vous créez une fonction logUser, elle devrait ressembler à cela :

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

Au cas où quelqu'un aurait la même question, ce qui m'a fait revenir ici:

Appel 

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

n’affecte que le security.context en cours pour la route utilisée. 

C'est à dire. vous ne pouvez connecter un utilisateur qu'à partir d'une URL sous le contrôle du pare-feu.

(Ajoutez une exception pour la route si nécessaire - IS_AUTHENTICATED_ANONYMOUSLY)

4
daemonl

En tant que problème déjà mentionné, ce paramètre $ fournisseurKey insaisissable n'est en réalité que le nom de votre règle de pare-feu, "foobar" dans le cas de l'exemple ci-dessous.

firewalls:
    foobar:
        pattern:    /foo/
2
Nim

J'ai essayé toutes les réponses ici et aucune n'a fonctionné. Le seul moyen pour authentifier mes utilisateurs sur un contrôleur est de créer une sous-demande, puis de le rediriger. Voici mon code, j'utilise silex mais vous pouvez facilement l'adapter à 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

Sur Symfony version 2.8.11 (fonctionnant probablement pour les versions plus anciennes et plus récentes), si vous utilisez FOSUserBundle procédez comme suit:

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.).
}

Pas besoin d'envoyer un événement comme je l'ai vu dans d'autres solutions.

inpired à partir de FOS\UserBundle\Controller\RegistrationController :: authenticateUser

(de composer.json version FOSUserBundle: "friendsofsymfony/user-bundle": "~ 1.3")

0
Nico