Autenticazione utente post-registrazione automatica


114

Stiamo costruendo un'app aziendale da zero in Symfony 2 e ho riscontrato un piccolo intoppo con il flusso di registrazione dell'utente: dopo che l'utente ha creato un account, dovrebbe essere automaticamente connesso con quelle credenziali, invece di essere immediatamente costretti a fornire nuovamente le proprie credenziali.

Qualcuno ha avuto esperienza con questo, o è in grado di indicarmi la giusta direzione?

Risposte:


146

Symfony 4.0

Questo processo non è cambiato da symfony 3 a 4 ma ecco un esempio che usa il nuovo AbstractController raccomandato. Sia il servizio che security.token_storagei sessionservizi sono registrati nel getSubscribedServicesmetodo genitore , quindi non è necessario aggiungerli nel controller.

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 partire da symfony 2.6 security.contextè deprecato a favore di security.token_storage. Il controller può ora essere semplicemente:

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

}

Sebbene sia deprecato, puoi comunque utilizzare security.context poiché è stato creato per essere retrocompatibile. Basta essere pronti per aggiornarlo per Symfony 3

Puoi leggere di più sulle modifiche 2.6 per la sicurezza qui: https://github.com/symfony/symfony/blob/2.6/UPGRADE-2.6.md

Symfony 2.3.x

Per ottenere ciò in symfony 2.3 non puoi più semplicemente impostare il token nel contesto di sicurezza. È inoltre necessario salvare il token nella sessione.

Supponendo un file di sicurezza con un firewall come:

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

E anche un'azione del controller simile:

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
    }

}

Per la creazione del token si desidera creare un UsernamePasswordToken, Questo accetta 4 parametri: Entità utente, Credenziali utente, Nome firewall, Ruoli utente. Non è necessario fornire le credenziali utente affinché il token sia valido.

Non sono sicuro al 100% che impostare il token su security.contextsia necessario se stai per reindirizzare immediatamente. Ma non sembra ferire, quindi l'ho lasciato.

Poi la parte importante, l'impostazione della variabile di sessione. La convenzione di denominazione delle variabili è _security_seguita dal nome del firewall, in questo caso mainmaking_security_main


1
Ho implementato il codice, l'utente ha effettuato l'accesso con successo, ma l'oggetto $ this-> getUser () restituisce NULL. Qualche idea?
saturo

2
Cose folli stavano accadendo senza $this->get('session')->set('_security_main', serialize($token));. Grazie, @Chausser!
Dmitriy

1
Con Symfony 2.6 se imposti il ​​token per un firewall chiamato mainE sei autenticato con un altro firewall chiamato admin(mentre stai impersonando l'utente), accade una cosa strana: _security_adminottiene il UsernamePasswordTokencon l'utente che hai fornito, cioè vieni "disconnesso" da il tuo adminfirewall. Qualche idea su come mantenere il token per il firewall "admin"?
gremo

1
Ad essere onesti, non sono sicuro che tu possa essere autenticato per 2 firewall allo stesso tempo, malato esaminarlo ma nel frattempo dovresti fare una domanda separata
Chase

3
@ Chausser è riuscito a farlo funzionare. La tua risposta è perfettamente corretta (ed è aggiornata), l'unica cosa che funziona solo quando chiami setToken(..)sotto lo stesso firewall di destinazione o senza essere ancora autenticato .
gremo

65

Ho capito questo, finalmente.

Dopo la registrazione dell'utente, dovresti avere accesso a un'istanza di oggetto di qualunque cosa tu abbia impostato come entità utente nella configurazione del tuo provider. La soluzione è creare un nuovo token con quell'entità utente e passarlo al contesto di sicurezza. Ecco un esempio basato sulla mia configurazione:

RegistrationController.php:

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

Dov'è mainil nome del firewall per la tua applicazione (grazie, @ Joe). Questo è davvero tutto quello che c'è da fare; il sistema ora considera il tuo utente completamente connesso come l'utente appena creato.

EDIT: Secondo il commento di @ Miquel, ho aggiornato l'esempio di codice del controller per includere un ruolo predefinito ragionevole per un nuovo utente (anche se ovviamente questo può essere regolato in base alle esigenze specifiche dell'applicazione).


2
Questo non è del tutto corretto con la versione di rilascio di Symfony 2. È necessario passare i ruoli dell'utente come quarto argomento al costruttore UsernamePasswordToken, altrimenti sarà contrassegnato come non autenticato e l'utente non avrà nessuno dei suoi ruoli.
Michael

E la bandiera "Ricordami"? Come accedere manualmente agli utenti, ma dovrebbero anche essere collegati per sempre. Questo pezzo di codice non risolve il problema.
maectpo

@maectpo che non rientrava nei miei requisiti originali, ma suona come un'ottima risposta di follow-up. Facci sapere cosa ti viene in mente.
Problematico

Ho un problema Posso loggarmi in questo modo, ma la variabile app.user è vuota. Conosci un modo per popolare questa variabile in questo processo di accesso? - Mando l'utente (stringa) e la password (stringa) come dice il riferimento: api.symfony.com/2.0/Symfony/Component/Security/Core/…
unairoldan

1
Come ha detto Marc di seguito, è necessario registrare lo spazio dei nomi UsernamePasswordToken:use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
MrGlass

6

Se hai un oggetto UserInterface (e questo dovrebbe essere il caso la maggior parte delle volte) potresti voler usare la funzione getRoles che implementa per l'ultimo argomento. Quindi, se crei una funzione logUser, dovrebbe apparire così:

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

6

Sto usando Symfony 2.2 e la mia esperienza è stata leggermente diversa da quella di Problematic , quindi questa è una versione combinata di tutte le informazioni di questa domanda più alcune delle mie.

Penso che Joe si sia sbagliato sul valore di $providerKey, il terzo parametro per il UsernamePasswordTokencostruttore. Dovrebbe essere la chiave di un provider di autenticazione (non utente). Viene utilizzato dal sistema di autenticazione per distinguere tra i token creati per diversi provider. Qualsiasi provider che discende da UserAuthenticationProviderautenticherà solo i token la cui chiave del provider corrisponde alla propria. Ad esempio, UsernamePasswordFormAuthenticationListenerimposta la chiave del token che crea in modo che corrisponda a quella del suo corrispondente DaoAuthenticationProvider. Ciò consente a un singolo firewall di avere più provider di nome utente e password senza che si calpestino a vicenda. Dobbiamo quindi scegliere una chiave che non sia in conflitto con altri provider. Io uso'new_user' .

Ho alcuni sistemi in altre parti della mia applicazione che dipendono dall'evento di successo dell'autenticazione e che non viene attivato semplicemente impostando il token sul contesto. Ho dovuto prendere il EventDispatcherdal contenitore e attivare manualmente l'evento. Ho deciso di non attivare anche un evento di accesso interattivo perché autentichiamo l'utente in modo implicito, non in risposta a una richiesta di accesso esplicita.

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

Si noti che l'uso di $this->get( .. )presuppone che lo snippet sia in un metodo controller. Se stai usando il codice da qualche altra parte, dovrai cambiarli per chiamarli ContainerInterface::get( ... )in modo appropriato all'ambiente. Come accade le mie entità utente implementano in UserInterfacemodo da poterle utilizzare direttamente con il token. In caso contrario, dovrai trovare un modo per convertirli inUserInterface istanze.

Quel codice funziona, ma mi sembra che stia hackerando l'architettura di autenticazione di Symfony piuttosto che lavorarci. Probabilmente sarebbe più corretto implementare un nuovo provider di autenticazione con la propria classe token piuttosto che dirottare il file UsernamePasswordToken. Inoltre, l'utilizzo di un provider appropriato significherebbe che gli eventi sono stati gestiti per te.


4

Nel caso qualcuno avesse la stessa domanda successiva che mi ha fatto tornare qui:

chiamata

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

ha effetto solo sulla corrente security.contextper il percorso utilizzato.

Vale a dire che puoi accedere a un utente solo da un URL all'interno del controllo del firewall.

(Aggiungi un'eccezione per il percorso se necessario - IS_AUTHENTICATED_ANONYMOUSLY)


1
sai come fai per una sessione? Piuttosto che solo per la richiesta corrente?
Jake N

3

Con Symfony 4.4, puoi semplicemente fare quanto segue nel tuo metodo del controller (vedi dalla documentazione di 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
        );
    }
}

Una cosa importante, assicurati che il tuo firewall non sia impostato su lazy. In tal caso, il token non verrà mai memorizzato nella sessione e non sarai mai connesso.

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

2

Come già accennato in precedenza, questo sfuggente parametro $ providerKey non è altro che il nome della regola del firewall, "foobar" nel caso dell'esempio seguente.

firewalls:
    foobar:
        pattern:    /foo/

Puoi spiegarmi perché se passo la stringa any ad esempio blablablacome terzo parametro a UsernamePasswordToken funzionerà anche tu? cosa significa questo parametro?
Mikhail

1
Questo parametro lega il tuo token a uno specifico provider di firewall. Nella maggior parte dei casi avrai un solo provider, quindi non preoccuparti.
Gottlieb Notschnabel

2

Ho provato tutte le risposte qui e nessuna ha funzionato. L'unico modo in cui posso autenticare i miei utenti su un controller è fare una sottorichiesta e quindi reindirizzare. Ecco il mio codice, sto usando silex ma puoi adattarlo facilmente a 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

Nella versione 2.8.11 di Symfony (probabilmente funzionante per versioni più vecchie e più recenti), se usi FOSUserBundle fai semplicemente questo:

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

Non è necessario inviare l'evento come ho visto in altre soluzioni.

ispirato da FOS \ UserBundle \ Controller \ RegistrationController :: authenticateUser

(dalla versione composer.json FOSUserBundle: "friendsofsymfony / user-bundle": "~ 1.3")

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.