Automatische Benutzerauthentifizierung nach der Registrierung

114

Wir erstellen eine Business-App von Grund auf in Symfony 2, und der Benutzerregistrierungsablauf ist ein wenig ins Wanken geraten: Nachdem der Benutzer ein Konto erstellt hat, sollten sie stattdessen automatisch mit diesen Anmeldeinformationen angemeldet werden sofort gezwungen zu sein, ihre Anmeldeinformationen erneut bereitzustellen.

Hat jemand Erfahrung damit oder kann er mich in die richtige Richtung weisen?

Problematisch
quelle

Antworten:

146

Symfony 4.0

Dieser Prozess hat sich nicht von Symfony 3 auf 4 geändert, aber hier ist ein Beispiel mit dem neu empfohlenen AbstractController. Sowohl der security.token_storageals auch der sessionDienst sind in der übergeordneten getSubscribedServicesMethode registriert , sodass Sie diese nicht in Ihrem Controller hinzufügen müssen.

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.

Ab Symfony ist 2.6 security.contextzugunsten von veraltet security.token_storage. Der Controller kann jetzt einfach sein:

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

}

Obwohl dies veraltet ist, können Sie es weiterhin verwenden, security.contextda es abwärtskompatibel ist. Seien Sie einfach bereit, es für Symfony 3 zu aktualisieren

Weitere Informationen zu den 2.6-Sicherheitsänderungen finden Sie hier: https://github.com/symfony/symfony/blob/2.6/UPGRADE-2.6.md

Symfony 2.3.x.

Um dies in Symfony 2.3 zu erreichen, können Sie das Token nicht mehr nur im Sicherheitskontext festlegen. Sie müssen das Token auch in der Sitzung speichern.

Angenommen, eine Sicherheitsdatei mit einer Firewall wie:

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

Und eine ähnliche Controller-Aktion:

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
    }

}

Für die Token-Erstellung möchten Sie Folgendes erstellen UsernamePasswordToken: Dies akzeptiert 4 Parameter: Benutzerentität, Benutzeranmeldeinformationen, Firewall-Name, Benutzerrollen. Sie müssen die Benutzeranmeldeinformationen nicht angeben, damit das Token gültig ist.

Ich bin nicht 100% sicher, dass das Setzen des Tokens auf dem security.contextnotwendig ist, wenn Sie nur sofort umleiten wollen. Aber es scheint nicht weh zu tun, also habe ich es verlassen.

Dann der wichtige Teil, das Setzen der Sitzungsvariablen. Die Variablen Namenskonvention wird _security_von der Firewall Namen, in diesem Fall gefolgt mainHerstellung_security_main

Verfolgungsjagd
quelle
1
Ich habe den Code implementiert, Benutzer erfolgreich protokolliert, aber das Objekt $ this-> getUser () gibt NULL zurück. Irgendeine Idee?
Sathish
2
Verrückte Dinge passierten ohne $this->get('session')->set('_security_main', serialize($token));. Danke, @Chausser!
Dmitriy
1
Wenn Sie mit Symfony 2.6 das Token für eine Firewall mit dem Namen mainAND festlegen und bei einer anderen Firewall mit dem Namen authentifiziert werden admin(während Sie sich als Benutzer ausgeben), passiert etwas Seltsames: Das _security_adminwird UsernamePasswordTokenmit dem von Ihnen angegebenen Benutzer abgerufen, dh Sie werden "getrennt" Ihre adminFirewall. Irgendeine Idee, wie man das Token für die "Admin" -Firewall verwaltet?
Gremo
1
Um ehrlich zu sein im nicht sicher , von Ihnen für 2 Firewalls zur gleichen Zeit, krank Blick hinein authentifiziert werden können , aber in der Zwischenzeit sollten Sie eine separate Frage stellen
Chase
3
@Chausser hat es geschafft, dass es funktioniert. Ihre Antwort ist vollkommen richtig (und sie ist aktualisiert). Sie funktioniert nur, wenn Sie setToken(..)unter derselben Zielfirewall anrufen oder noch nicht authentifiziert sind .
Gremo
65

Ich habe es endlich herausgefunden.

Nach der Benutzerregistrierung sollten Sie Zugriff auf eine Objektinstanz haben, die Sie in Ihrer Anbieterkonfiguration als Benutzerentität festgelegt haben. Die Lösung besteht darin, mit dieser Benutzerentität ein neues Token zu erstellen und es an den Sicherheitskontext zu übergeben. Hier ist ein Beispiel basierend auf meinem Setup:

RegistrationController.php:

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

Wo mainist der Name der Firewall für Ihre Anwendung (danke, @Joe). Das ist wirklich alles, was dazu gehört. Das System betrachtet Ihren Benutzer nun als den Benutzer, den er gerade erstellt hat, vollständig angemeldet.

BEARBEITEN: Gemäß dem Kommentar von @ Miquel habe ich das Controller-Codebeispiel aktualisiert, um eine sinnvolle Standardrolle für einen neuen Benutzer aufzunehmen (obwohl dies natürlich an die spezifischen Anforderungen Ihrer Anwendung angepasst werden kann).

Problematisch
quelle
2
Dies ist mit der Release-Version von Symfony 2 nicht ganz richtig. Sie müssen die Rollen des Benutzers als viertes Argument an den Konstruktor UsernamePasswordToken übergeben, da sie sonst als nicht authentifiziert markiert werden und der Benutzer keine seiner Rollen hat.
Michael
Was ist mit der Flagge "Erinnere dich an mich"? So melden Sie Benutzer von Hand an, aber sie sollten auch für immer angemeldet sein. Dieser Code löst dieses Problem nicht.
Maectpo
@maectpo, das nicht im Rahmen meiner ursprünglichen Anforderungen lag, aber nach einer großartigen Antwort klingt. Lassen Sie uns wissen, was Sie sich einfallen lassen.
Problematisch
Ich habe ein Problem. Ich kann mich auf diese Weise anmelden, aber die Variable app.user ist leer. Kennen Sie eine Möglichkeit, diese Variable in diesem Anmeldevorgang zu füllen? - Ich sende den Benutzer (Zeichenfolge) und das Kennwort (Zeichenfolge) wie folgt
unairoldan
1
Wie Marc unten sagte, müssen Sie den UsernamePasswordToken-Namespace registrieren:use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
MrGlass
6

Wenn Sie ein UserInterface-Objekt haben (und dies sollte die meiste Zeit der Fall sein), möchten Sie möglicherweise die Funktion getRoles verwenden, die es für das letzte Argument implementiert. Wenn Sie also eine Funktion logUser erstellen, sollte dies folgendermaßen aussehen:

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

Ich verwende Symfony 2.2 und meine Erfahrung war etwas anders als die von Problematic. Dies ist also eine kombinierte Version aller Informationen aus dieser Frage sowie einiger meiner eigenen.

Ich denke, Joe ist falsch in Bezug auf den Wert des $providerKeydritten Parameters für den UsernamePasswordTokenKonstruktor. Es soll der Schlüssel eines Authentifizierungsanbieters (nicht eines Benutzers) sein. Es wird vom Authentifizierungssystem verwendet, um zwischen Token zu unterscheiden, die für verschiedene Anbieter erstellt wurden. Jeder Anbieter, von dem er abstammt UserAuthenticationProvider, authentifiziert nur Token, deren Anbieterschlüssel mit seinem eigenen übereinstimmt. Beispielsweise UsernamePasswordFormAuthenticationListenersetzt der Schlüssel des von ihm erstellten Tokens so, dass er mit dem des entsprechenden Tokens übereinstimmt DaoAuthenticationProvider. Auf diese Weise verfügt eine einzelne Firewall über mehrere Anbieter von Benutzernamen und Kennwörtern, ohne dass diese aufeinander treten. Wir müssen daher einen Schlüssel auswählen, der nicht mit anderen Anbietern in Konflikt steht. Ich benutze 'new_user'.

Ich habe einige Systeme in anderen Teilen meiner Anwendung, die vom Authentifizierungserfolgsereignis abhängen und die nicht durch einfaches Setzen des Tokens auf den Kontext ausgelöst werden. Ich musste das EventDispatcheraus dem Container holen und das Ereignis manuell auslösen. Ich habe mich dagegen entschieden, auch ein interaktives Anmeldeereignis auszulösen, da wir den Benutzer implizit authentifizieren und nicht als Antwort auf eine explizite Anmeldeanforderung.

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

Beachten Sie, dass bei der Verwendung von $this->get( .. )davon ausgegangen wird, dass sich das Snippet in einer Controller-Methode befindet. Wenn Sie den Code an einer anderen Stelle verwenden, müssen Sie diese ändern, um sie ContainerInterface::get( ... )auf eine der Umgebung entsprechende Weise aufzurufen . Zufällig implementieren meine Benutzerentitäten, UserInterfacesodass ich sie direkt mit dem Token verwenden kann. Wenn dies bei Ihnen nicht der Fall ist, müssen Sie einen Weg finden, sie in UserInterfaceInstanzen zu konvertieren .

Dieser Code funktioniert, aber ich habe das Gefühl, dass er die Authentifizierungsarchitektur von Symfony hackt, anstatt damit zu arbeiten. Es wäre wahrscheinlich korrekter, einen neuen Authentifizierungsanbieter mit einer eigenen Token-Klasse zu implementieren, als den zu entführen UsernamePasswordToken. Die Verwendung eines geeigneten Anbieters würde auch bedeuten, dass die Ereignisse für Sie behandelt wurden.

Sam Hanes
quelle
4

Falls jemand die gleiche Folgefrage hat, die mich dazu gebracht hat, hierher zurückzukehren:

Berufung

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

wirkt sich nur auf den Strom security.contextfür die verwendete Route aus.

Das heißt, Sie können einen Benutzer nur über eine URL anmelden, die der Firewall unterliegt.

(Fügen Sie bei Bedarf eine Ausnahme für die Route hinzu - IS_AUTHENTICATED_ANONYMOUSLY)

Daemonl
quelle
1
Weißt du, wie du das für eine Sitzung machst? Anstatt nur für die aktuelle Anfrage?
Jake N
3

Mit Symfony 4.4 können Sie in Ihrer Controller-Methode einfach Folgendes tun (siehe Symfony-Dokumentation: 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
        );
    }
}

Eine wichtige Sache, stellen Sie sicher, dass Ihre Firewall nicht eingestellt ist lazy. Wenn dies der Fall ist, wird das Token niemals in der Sitzung gespeichert und Sie werden niemals angemeldet.

firewalls:
    main:
        anonymous: ~ # this and not 'lazy'
Etienne Noël
quelle
2

Wie hier bereits erwähnt, ist dieser schwer fassbare $ providerKey-Parameter in Wirklichkeit nichts anderes als der Name Ihrer Firewall-Regel, im folgenden Beispiel 'foobar'.

firewalls:
    foobar:
        pattern:    /foo/
Nim
quelle
Können Sie mir erklären, warum blablablaes auch funktioniert, wenn ich eine beliebige Zeichenfolge beispielsweise als dritten Parameter an UsernamePasswordToken übergebe? Was bedeutet dieser Parameter?
Mikhail
1
Dieser Parameter bindet Ihr Token an einen bestimmten Firewall-Anbieter. In den meisten Fällen haben Sie nur einen Anbieter, kümmern Sie sich also nicht darum.
Gottlieb Notschnabel
2

Ich habe alle Antworten hier ausprobiert und keine hat funktioniert. Die einzige Möglichkeit, meine Benutzer auf einem Controller zu authentifizieren, besteht darin, eine Unteranforderung zu stellen und dann umzuleiten. Hier ist mein Code, ich verwende Silex, aber Sie können ihn leicht an symfony2 anpassen:

$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'));
Diego Castro
quelle
1

Wenn Sie in Symfony Version 2.8.11 (wahrscheinlich für ältere und neuere Versionen) FOSUserBundle verwenden, gehen Sie wie folgt vor :

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

Keine Notwendigkeit, Ereignis auszulösen, wie ich in anderen Lösungen gesehen habe.

von FOS \ UserBundle \ Controller \ RegistrationController :: authenticateUser inspiriert

(aus der FOSUserBundle-Version von composer.json: "friendsofsymfony / user-bundle": "~ 1.3")

Nico
quelle