การตรวจสอบผู้ใช้หลังการลงทะเบียนอัตโนมัติ


114

เรากำลังสร้างแอปธุรกิจตั้งแต่ต้นใน Symfony 2 และฉันพบปัญหาเล็กน้อยกับขั้นตอนการลงทะเบียนผู้ใช้: หลังจากที่ผู้ใช้สร้างบัญชีผู้ใช้ควรเข้าสู่ระบบโดยอัตโนมัติด้วยข้อมูลรับรองเหล่านั้นแทน ของการถูกบังคับให้ระบุข้อมูลรับรองอีกครั้งในทันที

ใครมีประสบการณ์เกี่ยวกับเรื่องนี้หรือสามารถชี้ให้ฉันทราบในทิศทางที่ถูกต้อง?

คำตอบ:


146

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.contextเลิกใช้งานแล้วในความโปรดปรานของsecurity.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 ตัว ได้แก่ เอนทิตีผู้ใช้ข้อมูลรับรองผู้ใช้ชื่อไฟร์วอลล์บทบาทผู้ใช้ คุณไม่จำเป็นต้องระบุข้อมูลรับรองผู้ใช้เพื่อให้โทเค็นถูกต้อง

ฉันไม่แน่ใจ 100% ว่าการตั้งค่าโทเค็นsecurity.contextเป็นสิ่งที่จำเป็นหากคุณกำลังจะเปลี่ยนเส้นทางทันที แต่ดูเหมือนจะไม่เจ็บเลยฉันจึงทิ้งมันไป

จากนั้นส่วนที่สำคัญการตั้งค่าตัวแปรเซสชัน การประชุมการตั้งชื่อตัวแปรจะ_security_ตามด้วยชื่อไฟร์วอลล์ของคุณในกรณีนี้mainทำ_security_main


1
ฉันได้ติดตั้งรหัสผู้ใช้เข้าสู่ระบบเรียบร้อยแล้ว แต่วัตถุ $ this-> getUser () ส่งคืนค่า NULL ความคิดใด ๆ ?
sathish

2
สิ่งที่บ้าเกิดขึ้นโดยไม่$this->get('session')->set('_security_main', serialize($token));ได้ ขอบคุณ @Chausser!
Dmitriy

1
ด้วย Symfony 2.6 หากคุณตั้งค่าโทเค็นสำหรับไฟร์วอลล์ชื่อmainและคุณได้รับการรับรองความถูกต้องด้วยไฟร์วอลล์อื่นที่ชื่อadmin(ขณะที่คุณแอบอ้างเป็นผู้ใช้) สิ่งที่แปลกประหลาดเกิดขึ้น: สิ่งที่_security_adminได้รับUsernamePasswordTokenจากผู้ใช้ที่คุณระบุนั่นคือคุณได้รับ "ตัดการเชื่อมต่อ" จากadminไฟร์วอลล์ของคุณ มีความคิดอย่างไรในการรักษาโทเค็นสำหรับไฟร์วอลล์ "ผู้ดูแลระบบ"
gremo

1
พูดตามตรงฉันไม่แน่ใจว่าคุณสามารถรับรองความถูกต้องสำหรับไฟร์วอลล์ 2 เครื่องในเวลาเดียวกันดูไม่ดี แต่ในระหว่างนี้คุณควรถามคำถามแยกกัน
Chase

3
@ Chausser จัดการเพื่อให้มันใช้งานได้ คำตอบของคุณกำลังเป็นอย่างดีที่เหมาะสม (และก็ปรับปรุง) สิ่งเดียวที่มันจะทำงานเฉพาะเมื่อคุณเรียกsetToken(..)ภายใต้ไฟร์วอลล์เป้าหมายเดียวกันหรือโดยไม่ถูกรับรองความถูกต้องเลย
gremo

65

ในที่สุดก็คิดออก

หลังจากลงทะเบียนผู้ใช้คุณควรเข้าถึงอินสแตนซ์วัตถุของสิ่งที่คุณตั้งเป็นเอนทิตีผู้ใช้ของคุณในการกำหนดค่าผู้ให้บริการของคุณ วิธีแก้ปัญหาคือการสร้างโทเค็นใหม่กับเอนทิตีผู้ใช้นั้นและส่งไปยังบริบทความปลอดภัย นี่คือตัวอย่างตามการตั้งค่าของฉัน:

RegistrationController.php:

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

mainชื่อไฟร์วอลล์สำหรับแอปพลิเคชันของคุณอยู่ที่ไหน(ขอบคุณ @Joe) นั่นคือทั้งหมดที่มีให้จริงๆ ระบบจะถือว่าผู้ใช้ของคุณเข้าสู่ระบบโดยสมบูรณ์เป็นผู้ใช้ที่เพิ่งสร้างขึ้น

แก้ไข: ความคิดเห็นของ Per @ Miquel ฉันได้อัปเดตตัวอย่างโค้ดคอนโทรลเลอร์เพื่อรวมบทบาทเริ่มต้นที่เหมาะสมสำหรับผู้ใช้ใหม่ (แม้ว่าจะเห็นได้ชัดว่าสามารถปรับเปลี่ยนได้ตามความต้องการเฉพาะของแอปพลิเคชันของคุณ)


2
สิ่งนี้ไม่ถูกต้องกับ Symfony 2 เวอร์ชันที่วางจำหน่ายคุณต้องส่งต่อบทบาทของผู้ใช้เป็นอาร์กิวเมนต์ที่สี่ไปยังตัวสร้าง UsernamePasswordToken มิฉะนั้นจะถูกทำเครื่องหมายว่าไม่ได้รับการตรวจสอบความถูกต้องและผู้ใช้จะไม่มีบทบาทใด ๆ
Michael

แล้วธง "จดจำฉัน" ล่ะ วิธีการเข้าสู่ระบบผู้ใช้ด้วยมือ แต่พวกเขาควรเข้าสู่ระบบตลอดไป โค้ดส่วนนี้ไม่สามารถแก้ปัญหานั้นได้
maectpo

@maectpo ที่ไม่ได้อยู่ในขอบเขตของข้อกำหนดเดิมของฉัน แต่ดูเหมือนจะเป็นคำตอบที่ยอดเยี่ยม แจ้งให้เราทราบว่าคุณคิดอย่างไร
ปัญหา

ฉันมีปัญหา ฉันสามารถเข้าสู่ระบบด้วยวิธีนี้ แต่ฉันตัวแปร app.user ว่างเปล่า คุณรู้วิธีใดในการเติมตัวแปรนี้ในกระบวนการเข้าสู่ระบบนี้หรือไม่? - ฉันส่งผู้ใช้ (สตริง) และรหัสผ่าน (สตริง) ตามที่พูด Reference: api.symfony.com/2.0/Symfony/Component/Security/Core/…
unairoldan

1
เช่นเดียวกับที่ Marc กล่าวไว้ด้านล่างคุณต้องลงทะเบียน UsernamePasswordToken namespace:use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
MrGlass

6

หากคุณมีออบเจ็กต์ UserInterface (และเป็นกรณีส่วนใหญ่) คุณอาจต้องการใช้ฟังก์ชัน getRoles ที่ใช้กับอาร์กิวเมนต์สุดท้าย ดังนั้นหากคุณสร้างฟังก์ชัน logUser ควรมีลักษณะดังนี้:

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

6

ฉันใช้ Symfony 2.2 และประสบการณ์ของฉันแตกต่างจากProblematicเล็กน้อยดังนั้นนี่คือเวอร์ชันรวมของข้อมูลทั้งหมดจากคำถามนี้บวกกับข้อมูลบางส่วนของฉันเอง

ฉันคิดว่าโจผิดเกี่ยวกับค่าของ$providerKeyพารามิเตอร์ที่สามของตัวUsernamePasswordTokenสร้าง มันควรจะเป็นกุญแจสำคัญของผู้ให้บริการการพิสูจน์ตัวตน (ไม่ใช่ผู้ใช้) ระบบตรวจสอบสิทธิ์ใช้เพื่อแยกความแตกต่างระหว่างโทเค็นที่สร้างขึ้นสำหรับผู้ให้บริการที่แตกต่างกัน ผู้ให้บริการใด ๆ ที่สืบเชื้อสายมาUserAuthenticationProviderจะตรวจสอบสิทธิ์โทเค็นที่คีย์ผู้ให้บริการตรงกับของตนเองเท่านั้น ยกตัวอย่างเช่นชุดที่สำคัญของโทเค็นจะสร้างให้ตรงกับสอดคล้องกันUsernamePasswordFormAuthenticationListener DaoAuthenticationProviderซึ่งช่วยให้ไฟร์วอลล์เดียวมีผู้ให้บริการชื่อผู้ใช้ + รหัสผ่านหลายรายโดยที่พวกเขาไม่ต้องเหยียบกัน เราจึงจำเป็นต้องเลือกคีย์ที่จะไม่ขัดแย้งกับผู้ให้บริการรายอื่น ฉันใช้'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 . นอกจากนี้การใช้ผู้ให้บริการที่เหมาะสมจะหมายความว่าเหตุการณ์นั้นได้รับการจัดการสำหรับคุณ


4

เผื่อว่าใครมีคำถามตามมาเหมือนกันที่ทำให้ฉันต้องกลับมาที่นี่:

การเรียกร้อง

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

มีผลเฉพาะกระแสsecurity.contextสำหรับเส้นทางที่ใช้เท่านั้น

กล่าวคือคุณสามารถเข้าสู่ระบบได้เฉพาะผู้ใช้จาก url ที่อยู่ในการควบคุมของไฟร์วอลล์

(เพิ่มข้อยกเว้นสำหรับเส้นทางหากจำเป็น - IS_AUTHENTICATED_ANONYMOUSLY)


1
คุณรู้หรือไม่ว่าคุณทำสิ่งนี้สำหรับเซสชันนี้อย่างไร แทนที่จะเป็นเพียงคำขอปัจจุบัน?
Jake N

3

ด้วย Symfony 4.4 คุณสามารถทำสิ่งต่อไปนี้ในวิธีการควบคุมของคุณ (ดูจากเอกสาร 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
        );
    }
}

สิ่งสำคัญอย่างหนึ่งตรวจสอบให้แน่ใจว่าไม่ได้ตั้งค่าไฟร์วอลล์ของคุณเป็น lazyสิ่งหนึ่งที่สำคัญให้แน่ใจว่าไฟร์วอลล์ของคุณไม่ได้ตั้งค่าหากเป็นเช่นนั้นโทเค็นจะไม่ถูกเก็บไว้ในเซสชันและคุณจะไม่เข้าสู่ระบบ

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

2

ดังที่ได้กล่าวไปแล้วที่เป็นปัญหาพารามิเตอร์ $ providerKey ที่เข้าใจยากนี้ในความเป็นจริงแล้วไม่มีอะไรมากไปกว่าชื่อของกฎไฟร์วอลล์ของคุณ 'foobar' ในกรณีของตัวอย่างด้านล่าง

firewalls:
    foobar:
        pattern:    /foo/

คุณช่วยอธิบายได้ไหมว่าทำไมถ้าฉันส่งสตริงใด ๆ เช่นblablablaพารามิเตอร์ที่สามไปยัง UsernamePasswordToken มันก็จะใช้ได้เช่นกัน พารามิเตอร์นี้หมายถึงอะไร?
Mikhail

1
พารามิเตอร์นี้ผูกโทเค็นของคุณกับผู้ให้บริการไฟร์วอลล์เฉพาะ ในกรณีส่วนใหญ่คุณจะมีผู้ให้บริการเพียงรายเดียวดังนั้นอย่ากังวลกับมัน
Gottlieb Notschnabel

2

ฉันลองใช้คำตอบทั้งหมดที่นี่แล้วและไม่มีผล วิธีเดียวที่ฉันสามารถตรวจสอบสิทธิ์ผู้ใช้ของฉันบนคอนโทรลเลอร์คือการส่งคำขอย่อยจากนั้นเปลี่ยนเส้นทาง นี่คือรหัสของฉันฉันใช้ 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'));

1

ใน 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

(จากเวอร์ชัน comper.json FOSUserBundle: "friendsofsymfony / user-bundle": "~ 1.3")

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.