Otentikasi pengguna pasca-pendaftaran otomatis

114

Kami sedang membangun aplikasi bisnis dari nol di Symfony 2, dan saya mengalami sedikit masalah dengan alur pendaftaran pengguna: setelah pengguna membuat akun, mereka harus masuk secara otomatis dengan kredensial tersebut, sebagai gantinya segera dipaksa untuk memberikan kredensial mereka lagi.

Adakah yang punya pengalaman dengan ini, atau bisa mengarahkan saya ke arah yang benar?

Bermasalah
sumber

Jawaban:

146

Symfony 4.0

Proses ini tidak berubah dari symfony 3 ke 4 tetapi berikut adalah contoh penggunaan AbstractController yang baru direkomendasikan. Baik security.token_storagedan sessionlayanan terdaftar di induk getSubscribedServicesmetode sehingga Anda tidak perlu menambah orang-orang di controller Anda.

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

Pada symfony 2.6 security.contextsudah usang dan tidak mendukung security.token_storage. Pengontrol sekarang dapat menjadi:

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

}

Meskipun ini sudah usang, Anda masih dapat menggunakan security.contextkarena telah dibuat agar kompatibel dengan versi sebelumnya. Bersiaplah untuk memperbaruinya untuk Symfony 3

Anda dapat membaca lebih lanjut tentang 2.6 perubahan untuk keamanan di sini: https://github.com/symfony/symfony/blob/2.6/UPGRADE-2.6.md

Symfony 2.3.x

Untuk melakukannya di symfony 2.3 Anda tidak bisa lagi hanya mengatur token dalam konteks keamanan. Anda juga perlu menyimpan token ke sesi tersebut.

Dengan asumsi file keamanan dengan firewall seperti:

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

Dan tindakan pengontrol serupa juga:

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
    }

}

Untuk pembuatan token Anda akan ingin membuat UsernamePasswordToken, Ini menerima 4 parameter: Entitas Pengguna, Kredensial Pengguna, Nama Firewall, Peran Pengguna. Anda tidak perlu memberikan kredensial pengguna agar token menjadi valid.

Saya tidak 100% yakin bahwa menyetel token pada security.contextdiperlukan jika Anda hanya akan langsung mengalihkan. Tapi sepertinya tidak sakit jadi saya tinggalkan.

Kemudian bagian yang penting, pengaturan variabel sesi. Konvensi penamaan variabel _security_diikuti dengan nama firewall Anda, dalam hal ini mainpembuatan_security_main

Mengejar
sumber
1
Saya telah menerapkan kode, Pengguna berhasil masuk, tetapi objek $ this-> getUser () mengembalikan NULL. Ada ide?
sathish
2
Hal-hal gila terjadi tanpa $this->get('session')->set('_security_main', serialize($token));. Terima kasih, @Chausser!
Dmitriy
1
Dengan Symfony 2.6 jika Anda menyetel token untuk firewall bernama mainDAN Anda diautentikasi dengan firewall lain bernama admin(karena Anda meniru pengguna), hal yang aneh terjadi: Anda _security_adminmendapatkan UsernamePasswordTokenpengguna yang Anda berikan, yaitu Anda "terputus" dari adminfirewall Anda . Ada ide bagaimana cara mempertahankan token untuk firewall "admin"?
gremo
1
Sejujurnya saya tidak yakin Anda dapat diautentikasi untuk 2 firewall pada saat yang sama, sakit melihatnya tetapi sementara itu Anda harus mengajukan pertanyaan terpisah
Chase
3
@Chausser berhasil membuatnya bekerja. Jawaban Anda benar (dan diperbarui), satu-satunya hal yang berfungsi hanya ketika Anda memanggil di setToken(..)bawah firewall target yang sama atau belum diautentikasi .
gremo
65

Menemukan yang ini, akhirnya.

Setelah pendaftaran pengguna, Anda harus memiliki akses ke instance objek apa pun yang telah Anda tetapkan sebagai entitas pengguna Anda dalam konfigurasi penyedia Anda. Solusinya adalah membuat token baru dengan entitas pengguna tersebut dan meneruskannya ke konteks keamanan. Berikut adalah contoh berdasarkan penyiapan saya:

RegistrationController.php:

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

Dimana main nama firewall untuk aplikasi Anda (terima kasih, @Joe). Hanya itu yang ada di sana; sistem sekarang menganggap pengguna Anda masuk sepenuhnya sebagai pengguna yang baru saja mereka buat.

EDIT: Sesuai komentar @ Miquel, saya telah memperbarui sampel kode pengontrol untuk menyertakan peran default yang masuk akal untuk pengguna baru (meskipun jelas ini dapat disesuaikan sesuai dengan kebutuhan spesifik aplikasi Anda).

Bermasalah
sumber
2
Ini kurang tepat dengan versi rilis Symfony 2. Anda harus meneruskan peran pengguna sebagai argumen keempat ke konstruktor UsernamePasswordToken, atau akan ditandai sebagai tidak diautentikasi dan pengguna tidak akan memiliki peran apa pun.
Michael
Bagaimana dengan bendera "Ingat saya"? Bagaimana cara login pengguna dengan tangan, tetapi juga mereka harus login selamanya. Potongan kode ini tidak menyelesaikan masalah itu.
maectpo
@maectpo yang tidak termasuk dalam cakupan persyaratan asli saya, tetapi terdengar seperti jawaban tindak lanjut yang bagus. Beri tahu kami hasil yang Anda dapatkan.
Bermasalah
Saya punya masalah. Saya bisa masuk dengan cara ini, tapi saya variabel app.user kosong. Apakah Anda tahu cara mengisi variabel ini dalam proses masuk ini? - Saya mengirim pengguna (string) dan kata sandi (string) sebagai Referensi: api.symfony.com/2.0/Symfony/Component/Security/Core/…
unairoldan
1
Seperti yang dikatakan Marc di bawah ini, Anda perlu mendaftarkan namespace UsernamePasswordToken:use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
MrGlass
6

Jika Anda memiliki objek UserInterface (dan seharusnya itu yang paling sering terjadi) Anda mungkin ingin menggunakan fungsi getRoles yang diimplementasikannya untuk argumen terakhir. Jadi jika Anda membuat fungsi logUser, akan terlihat seperti ini:

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

Saya menggunakan Symfony 2.2 dan pengalaman saya sedikit berbeda dari Problematic , jadi ini adalah versi gabungan dari semua info dari pertanyaan ini ditambah beberapa dari saya sendiri.

Saya pikir Joe salah tentang nilai $providerKey, parameter ketiga untuk UsernamePasswordTokenkonstruktor. Ini seharusnya menjadi kunci penyedia otentikasi (bukan pengguna). Ini digunakan oleh sistem otentikasi untuk membedakan antara token yang dibuat untuk penyedia yang berbeda. Penyedia apa pun yang turun dari UserAuthenticationProviderhanya akan mengautentikasi token yang kunci penyedia cocok dengan miliknya sendiri. Misalnya, UsernamePasswordFormAuthenticationListenerset kunci dari token yang dibuatnya agar sesuai dengan yang terkait DaoAuthenticationProvider. Itu memungkinkan satu firewall memiliki beberapa penyedia nama pengguna + kata sandi tanpa mereka menginjak satu sama lain. Oleh karena itu, kami perlu memilih kunci yang tidak akan bertentangan dengan penyedia lain. Saya menggunakan 'new_user'.

Saya memiliki beberapa sistem di bagian lain aplikasi saya yang bergantung pada peristiwa keberhasilan autentikasi , dan itu tidak diaktifkan hanya dengan menyetel token pada konteksnya. Saya harus mendapatkan EventDispatcherdari wadah dan mengaktifkan acara secara manual. Saya memutuskan untuk tidak mengaktifkan acara login interaktif karena kami mengautentikasi pengguna secara implisit, bukan sebagai tanggapan atas permintaan login eksplisit.

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

Perhatikan bahwa penggunaan $this->get( .. )mengasumsikan cuplikan ada dalam metode pengontrol. Jika Anda menggunakan kode di tempat lain, Anda harus mengubahnya untuk dipanggil ContainerInterface::get( ... )dengan cara yang sesuai dengan lingkungan. Kebetulan entitas pengguna saya menerapkan UserInterfacesehingga saya dapat menggunakannya secara langsung dengan token. Jika milik Anda tidak, Anda harus menemukan cara untuk mengubahnya menjadi UserInterfaceinstance.

Kode itu berfungsi, tetapi saya merasa seperti meretas arsitektur otentikasi Symfony daripada bekerja dengannya. Mungkin akan lebih tepat untuk mengimplementasikan penyedia otentikasi baru dengan kelas tokennya sendiri daripada membajak UsernamePasswordToken. Selain itu, menggunakan penyedia yang tepat berarti acara tersebut ditangani untuk Anda.

Sam Hanes
sumber
4

Jika ada orang yang memiliki pertanyaan lanjutan yang sama yang membuat saya kembali ke sini:

Panggilan

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

hanya mempengaruhi arus security.contextuntuk rute yang digunakan.

Yaitu Anda hanya dapat login pengguna dari url dalam kendali firewall.

(Tambahkan pengecualian untuk rute jika diperlukan - IS_AUTHENTICATED_ANONYMOUSLY)

daemonl
sumber
1
apakah Anda tahu bagaimana Anda melakukan ini untuk satu sesi? Daripada hanya untuk permintaan saat ini?
Jake N
3

Dengan Symfony 4.4, Anda cukup melakukan hal berikut dalam metode pengontrol Anda (lihat dari dokumentasi 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
        );
    }
}

Satu hal penting, pastikan firewall Anda tidak disetel ke lazy. Jika ya, token tidak akan pernah disimpan dalam sesi dan Anda tidak akan pernah bisa masuk.

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

Seperti Bermasalah di sini telah disebutkan, parameter $ providerKey yang sulit dipahami ini sebenarnya tidak lebih dari nama aturan firewall Anda, 'foobar' dalam contoh di bawah ini.

firewalls:
    foobar:
        pattern:    /foo/
Nim
sumber
Dapatkah Anda menjelaskan kepada saya mengapa jika saya meneruskan string apa pun misalnya blablablasebagai parameter ketiga ke UsernamePasswordToken, itu akan berfungsi juga? apa arti parameter ini?
Mikhail
1
Parameter ini mengikat token Anda ke penyedia firewall tertentu. Dalam kebanyakan kasus, Anda hanya akan memiliki satu penyedia, jadi jangan repot-repot.
Gottlieb Notschnabel
2

Saya mencoba semua jawaban di sini dan tidak ada yang berhasil. Satu-satunya cara saya dapat mengotentikasi pengguna saya pada pengontrol adalah dengan membuat subrequest dan kemudian mengalihkan. Ini kode saya, saya menggunakan silex tetapi Anda dapat dengan mudah menyesuaikannya ke 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'));
Diego Castro
sumber
1

Pada Symfony versi 2.8.11 (mungkin berfungsi untuk versi yang lebih lama dan yang lebih baru), jika Anda menggunakan FOSUserBundle cukup lakukan ini:

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

Tidak perlu mengirimkan acara seperti yang pernah saya lihat di solusi lain.

terinspirasi dari FOS \ UserBundle \ Controller \ RegistrationController :: authenticateUser

(dari versi FOSUserBundle composer.json: "friendsofsymfony / user-bundle": "~ 1.3")

Nico
sumber