Tự động xác thực người dùng sau đăng ký


114

Chúng tôi đang xây dựng một ứng dụng dành cho doanh nghiệp từ đầu trong Symfony 2 và tôi đã gặp một chút khó khăn với quy trình đăng ký người dùng: sau khi người dùng tạo tài khoản, họ sẽ tự động đăng nhập bằng những thông tin đăng nhập đó, thay vào đó bị buộc phải cung cấp lại thông tin đăng nhập của họ ngay lập tức.

Bất cứ ai có bất kỳ kinh nghiệm với điều này, hoặc có thể chỉ cho tôi đúng hướng?

Câu trả lời:


146

Symfony 4.0

Quá trình này không thay đổi từ symfony 3 thành 4 nhưng đây là một ví dụ sử dụng AbstractController mới được đề xuất. Cả dịch vụ security.token_storagesessiondịch vụ đều được đăng ký trong getSubscribedServicesphương thức chính vì vậy bạn không cần phải thêm chúng vào bộ điều khiển của mình.

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

Vì symfony 2.6 security.contextkhông được dùng nữa security.token_storage. Bộ điều khiển bây giờ có thể đơn giản là:

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

}

Mặc dù tính năng này không được dùng nữa, bạn vẫn có thể sử dụng security.contextvì nó đã được tạo ra để tương thích ngược. Chỉ cần sẵn sàng cập nhật nó cho Symfony 3

Bạn có thể đọc thêm về các thay đổi 2.6 đối với bảo mật tại đây: https://github.com/symfony/symfony/blob/2.6/UPGRADE-2.6.md

Symfony 2.3.x

Để thực hiện điều này trong symfony 2.3, bạn không thể chỉ đặt mã thông báo trong ngữ cảnh bảo mật nữa. Bạn cũng cần lưu mã thông báo vào phiên.

Giả sử một tệp bảo mật có tường lửa như:

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

Và một hành động điều khiển cũng tương tự:

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
    }

}

Để tạo mã thông báo, bạn sẽ muốn tạo UsernamePasswordToken, Điều này chấp nhận 4 tham số: Thực thể người dùng, Thông tin đăng nhập người dùng, Tên tường lửa, Vai trò người dùng. Bạn không cần cung cấp thông tin xác thực của người dùng để mã thông báo hợp lệ.

Tôi không chắc chắn 100% rằng việc đặt mã thông báo vào security.contextlà cần thiết nếu bạn định chuyển hướng ngay lập tức. Nhưng nó có vẻ không đau nên tôi đã bỏ nó.

Sau đó là phần quan trọng, thiết lập biến phiên. Quy ước đặt tên biến được _security_theo sau bởi tên tường lửa của bạn, trong trường hợp này mainlàm_security_main


1
Tôi đã triển khai mã, Người dùng đã đăng nhập thành công, nhưng đối tượng $ this-> getUser () trả về NULL. Bất kỳ ý tưởng?
sathish

2
Những điều điên rồ đã xảy ra mà không có $this->get('session')->set('_security_main', serialize($token));. Cảm ơn bạn, @Chausser!
Dmitriy

1
Với Symfony 2.6 nếu bạn đặt mã thông báo cho tường lửa có tên mainVÀ bạn được xác thực với một tường lửa khác có tên admin(vì bạn đang mạo danh người dùng), một điều kỳ lạ sẽ xảy ra: kết _security_adminnối UsernamePasswordTokenvới người dùng mà bạn đã cung cấp, tức là bạn bị "ngắt kết nối" khỏi admintường lửa của bạn . Bất kỳ ý tưởng nào về cách duy trì mã thông báo cho tường lửa "quản trị"?
gremo

1
Để trở thành im Thành thật mà nói không chắc chắn của bạn có thể được chứng thực cho 2 bức tường lửa cùng một lúc, nhìn ốm vào nó, nhưng trong thời gian trung bình bạn nên đặt một câu hỏi riêng biệt
Chase

3
@Chausser đã quản lý để làm cho nó hoạt động. Câu trả lời của bạn là hoàn toàn đúng (và nó đã được cập nhật), điều duy nhất nó chỉ hoạt động khi bạn gọi setToken(..)dưới cùng một tường lửa mục tiêu hoặc khi chưa được xác thực .
gremo

65

Cuối cùng cũng tìm ra cái này.

Sau khi đăng ký người dùng, bạn sẽ có quyền truy cập vào một phiên bản đối tượng của bất kỳ thứ gì bạn đã đặt làm thực thể người dùng trong cấu hình nhà cung cấp của mình. Giải pháp là tạo mã thông báo mới với thực thể người dùng đó và chuyển nó vào ngữ cảnh bảo mật. Đây là một ví dụ dựa trên thiết lập của tôi:

RegistrationController.php:

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

Ở đâu main là tên của các bức tường lửa cho ứng dụng của bạn (cảm ơn, @Joe). Đó thực sự là tất cả những gì cần có; hệ thống hiện coi người dùng của bạn đã đăng nhập đầy đủ với tư cách là người dùng mà họ vừa tạo.

CHỈNH SỬA: Theo nhận xét của @ Miquel, tôi đã cập nhật mẫu mã bộ điều khiển để bao gồm vai trò mặc định hợp lý cho người dùng mới (mặc dù rõ ràng điều này có thể được điều chỉnh theo nhu cầu cụ thể của ứng dụng của bạn).


2
Điều này không hoàn toàn đúng với phiên bản phát hành của Symfony 2. Bạn cần chuyển vai trò của người dùng làm đối số thứ tư cho hàm tạo UsernamePasswordToken, nếu không nó sẽ được đánh dấu là chưa được xác thực và người dùng sẽ không có bất kỳ vai trò nào của họ.
Michael

Còn cờ "Nhớ anh" thì sao? Cách đăng nhập người dùng bằng tay, nhưng họ cũng phải đăng nhập mãi mãi. Đoạn mã này không giải quyết được vấn đề đó.
maectpo

@maectpo không nằm trong phạm vi yêu cầu ban đầu của tôi, nhưng có vẻ như một câu trả lời tiếp theo tuyệt vời. Hãy cho chúng tôi biết bạn nghĩ ra gì.
Có vấn đề

Tôi có một vấn đề. Tôi có thể đăng nhập theo cách này, nhưng biến app.user trống. Bạn có biết bất kỳ cách nào để điền biến này trong quá trình đăng nhập này không? - Tôi gửi cho người dùng (chuỗi) và mật khẩu (chuỗi) như nói Tham khảo: api.symfony.com/2.0/Symfony/Component/Security/Core/…
unairoldan

1
Giống như Marc nói dưới đây, bạn cần đăng ký namespace UsernamePasswordToken:use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
MrGlass

6

Nếu bạn có đối tượng UserInterface (và đó là trường hợp thường xảy ra), bạn có thể muốn sử dụng hàm getRoles mà nó triển khai cho đối số cuối cùng. Vì vậy, nếu bạn tạo một hàm logUser, nó sẽ giống như sau:

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

6

Tôi đang sử dụng Symfony 2.2 và trải nghiệm của tôi hơi khác so với Problematic , vì vậy đây là phiên bản tổng hợp của tất cả thông tin từ câu hỏi này cộng với một số thông tin của riêng tôi.

Tôi nghĩ rằng Joe đã sai về giá trị của $providerKey, tham số thứ ba đối với hàm UsernamePasswordTokentạo. Nó phải là khóa của một nhà cung cấp xác thực (không phải người dùng). Nó được hệ thống xác thực sử dụng để phân biệt giữa các mã thông báo được tạo cho các nhà cung cấp khác nhau. Bất kỳ nhà cung cấp nào xuất phát từ đó UserAuthenticationProvidersẽ chỉ xác thực các mã thông báo có khóa của nhà cung cấp khớp với chính nó. Ví dụ: UsernamePasswordFormAuthenticationListenerbộ khóa của mã thông báo mà nó tạo ra để khớp với khóa tương ứng của nó DaoAuthenticationProvider. Điều đó cho phép một tường lửa duy nhất có nhiều nhà cung cấp tên người dùng + mật khẩu mà không cần họ chen lấn lẫn nhau. Do đó, chúng tôi cần chọn một khóa không xung đột với bất kỳ nhà cung cấp nào khác. tôi sử dụng'new_user' .

Tôi có một vài hệ thống trong các phần khác của ứng dụng của mình phụ thuộc vào sự kiện xác thực thành công và điều đó không được kích hoạt bằng cách chỉ đặt mã thông báo trên ngữ cảnh. Tôi phải lấy EventDispatchertừ thùng chứa và kích hoạt sự kiện theo cách thủ công. Tôi quyết định không kích hoạt cả sự kiện đăng nhập tương tác vì chúng tôi đang xác thực người dùng một cách ngầm định, không phải để đáp ứng yêu cầu đăng nhập rõ ràng.

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

Lưu ý rằng việc sử dụng $this->get( .. )giả định đoạn mã nằm trong phương thức trình điều khiển. Nếu bạn đang sử dụng mã ở một nơi khác, bạn sẽ phải thay đổi chúng để gọi ContainerInterface::get( ... )theo cách phù hợp với môi trường. Khi nó xảy ra, các thực thể người dùng của tôi sẽ triển khai UserInterfaceđể tôi có thể sử dụng chúng trực tiếp với mã thông báo. Nếu không, bạn sẽ phải tìm cách chuyển đổi chúng thànhUserInterface phiên bản.

Mã đó hoạt động, nhưng tôi cảm thấy như nó đang tấn công kiến ​​trúc xác thực của Symfony hơn là làm việc với nó. Có lẽ sẽ đúng hơn nếu triển khai một nhà cung cấp xác thực mới với lớp mã thông báo của riêng nó hơn là chiếm quyền điều khiển UsernamePasswordToken. Ngoài ra, sử dụng một nhà cung cấp thích hợp sẽ có nghĩa là các sự kiện đã được xử lý cho bạn.


4

Trong trường hợp có ai có cùng câu hỏi tiếp theo khiến tôi phải quay lại đây:

Kêu gọi

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

chỉ ảnh hưởng hiện tại security.contextcho tuyến đường được sử dụng.

Tức là bạn chỉ có thể đăng nhập một người dùng từ một url trong tầm kiểm soát của tường lửa.

(Thêm một ngoại lệ cho tuyến đường nếu cần - IS_AUTHENTICATED_ANONYMOUSLY)


1
bạn có biết làm thế nào bạn làm điều này cho một phiên? Thay vì chỉ cho yêu cầu hiện tại?
Jake N

3

Với Symfony 4.4, bạn chỉ cần thực hiện những việc sau trong phương thức controller của mình (xem từ tài liệu Symfony: https://symfony.com/doc/current/security/guard_authentication.html#manently-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
        );
    }
}

Một điều quan trọng, hãy đảm bảo rằng tường lửa của bạn không được đặt thành lazy. Nếu đúng như vậy, mã thông báo sẽ không bao giờ được lưu trữ trong phiên và bạn sẽ không bao giờ đăng nhập được.

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

2

Như Problematic ở đây đã được đề cập ở đây, thông số $ providerKey khó nắm bắt này trên thực tế không khác gì tên của quy tắc tường lửa của bạn, 'foobar' trong trường hợp của ví dụ bên dưới.

firewalls:
    foobar:
        pattern:    /foo/

Bạn có thể giải thích cho tôi tại sao nếu tôi chuyển một chuỗi bất kỳ, ví dụ blablablanhư tham số thứ ba vào UsernamePasswordToken thì nó cũng sẽ hoạt động không? tham số này có nghĩa là gì?
Mikhail

1
Tham số này liên kết mã thông báo của bạn với một nhà cung cấp tường lửa cụ thể. Trong hầu hết các trường hợp, bạn sẽ chỉ có một nhà cung cấp, vì vậy đừng bận tâm về điều đó.
Gottlieb Notschnabel

2

Tôi đã thử tất cả các câu trả lời ở đây và không có câu trả lời nào hiệu quả. Cách duy nhất tôi có thể xác thực người dùng của mình trên bộ điều khiển là thực hiện một yêu cầu phụ và sau đó chuyển hướng. Đây là mã của tôi, tôi đang sử dụng silex nhưng bạn có thể dễ dàng điều chỉnh nó cho 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

Trên phiên bản Symfony 2.8.11 (có thể hoạt động cho các phiên bản cũ hơn và mới hơn), nếu bạn sử dụng FOSUserBundle, chỉ cần thực hiện điều này:

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

Không cần gửi sự kiện như tôi đã thấy trong các giải pháp khác.

hết hạn từ FOS \ UserBundle \ Controller \ RegisterController :: authenticateUser

(từ phiên bản composer.json FOSUserBundle: "friendsofsymfony / user-pack": "~ 1.3")

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.