[Symfony 5] Autorización separada para administradores y usuarios con dos entidades diferentes y formularios de inicio de sesión

propósito



Crea una autorización por separado en Symfony 5:



  • Administrador: tendrá la entidad de administración , URL de inicio de sesión / admin / login
  • Usuario: tendrá una entidad de usuario , URL de inicio de sesión / inicio de sesión
  • Los datos de inicio de sesión no deben superponerse, no podemos iniciar sesión como Usuario en la página / admin / login
  • Deben crearse dos entidades diferentes.
  • Se deben crear dos controladores de inicio de sesión diferentes y dos Seguridad diferentes
  • Posibilidad de configurar el reenvío después de la autorización por separado
  • La capacidad de usar diferentes datos de autorización (por ejemplo, para el Usuario queremos que los usuarios ingresen correo electrónico / contraseña, y para que el Administrador brinde protección adicional al agregar algún tipo de Uuid


¿Por qué se necesita esta guía?



Mi tarea consistía en dividir el formulario de inicio de sesión con la entidad Usuario en dos diferentes: para el usuario (entidad Usuario) y para el administrador (entidad Admin) para la funcionalidad normal del panel de administración (en este caso, EasyAdmin).



En este tutorial, describiré la ruta completa paso a paso, comenzando con la instalación del marco en sí y terminando con la creación de dos formas diferentes de autorización.



Especificaciones



  • Windows 10
  • OpenServer 5.3.7
  • PHP 7.4
  • MariaDB-10.2.12
  • Symfony 5.1


El tutorial es relevante a finales de junio de 2020.



Paso 0 - Instala Symfony 5



Suponemos que ha instalado todos los componentes necesarios, incluido Composer en el directorio raíz de OpenServer (... / domains).



composer create-project symfony/website-skeleton auth_project




Paso 1 - configurando la base de datos



Cree una nueva base de datos, asígnele el nombre auth_project, deje que la contraseña y el usuario sean mysql. Ahora necesitamos redefinir la configuración de .env.



Debería ser así:




# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
#  * .env                contains default values for the environment variables needed by the app
#  * .env.local          uncommitted file with local overrides
#  * .env.$APP_ENV       committed environment-specific defaults
#  * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration

###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=16cbb669c87ff9259c522ee2846cb397
#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
#TRUSTED_HOSTS='^(localhost|example\.com)$'
###< symfony/framework-bundle ###

###> symfony/mailer ###
# MAILER_DSN=smtp://localhost
###< symfony/mailer ###

###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# For a PostgreSQL database, use: "postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8"
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
DATABASE_URL=mysql://mysql:mysql@127.0.0.1:3306/auth_project?serverVersion=mariadb-10.2.12
###< doctrine/doctrine-bundle ###



Paso 2: creación de la entidad de usuario



Cree la entidad Usuario, seleccione el correo electrónico como un valor único



php bin/console make:user


imagen



Paso 3: crear una entidad administrativa



Repetimos todo lo que se describe en el paso anterior, en lugar del nombre de la entidad Usuario, coloque Admin



Paso 4: preparar accesorios



Creemos 2 cuentas de prueba, una para Usuario y otra para Administrador. Usaremos DoctrineFixturesBundle



Primero debes ponerlo



composer require --dev orm-fixtures


Después de la instalación, la carpeta DataFixtures aparecerá en / src, en la que ya se habrá creado el archivo AppFixtures.php.



Cámbiele el nombre a UserFixtures.php y agréguele la funcionalidad necesaria.



<?php

namespace App\DataFixtures;

use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class UserFixtures extends Fixture
{
    private $encoder;

    private $em;

    public function __construct(UserPasswordEncoderInterface $encoder, EntityManagerInterface $entityManager)
    {
        $this->encoder = $encoder;
        $this->em = $entityManager;
    }

    public function load(\Doctrine\Persistence\ObjectManager $manager)
    {
        $usersData = [
              0 => [
                  'email' => 'user@example.com',
                  'role' => ['ROLE_USER'],
                  'password' => 123654
              ]
        ];

        foreach ($usersData as $user) {
            $newUser = new User();
            $newUser->setEmail($user['email']);
            $newUser->setPassword($this->encoder->encodePassword($newUser, $user['password']));
            $newUser->setRoles($user['role']);
            $this->em->persist($newUser);
        }

        $this->em->flush();
    }
}


Lo mismo debe hacerse para el administrador: cree AdminFixtures.php



<?php

namespace App\DataFixtures;

use App\Entity\Admin;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class AdminFixtures extends Fixture
{
    private $encoder;

    private $em;

    public function __construct(UserPasswordEncoderInterface $encoder, EntityManagerInterface $entityManager)
    {
        $this->encoder = $encoder;
        $this->em = $entityManager;
    }

    public function load(\Doctrine\Persistence\ObjectManager $manager)
    {
        $adminsData = [
              0 => [
                  'email' => 'admin@example.com',
                  'role' => ['ROLE_ADMIN'],
                  'password' => 123654
              ]
        ];

        foreach ($adminsData as $admin) {
            $newAdmin = new Admin();
            $newAdmin->setEmail($admin['email']);
            $newAdmin->setPassword($this->encoder->encodePassword($newAdmin, $admin['password']));
            $newAdmin->setRoles($admin['role']);
            $this->em->persist($newAdmin);
        }

        $this->em->flush();
    }
}


Paso 5: cargue las migraciones y los accesorios en la base de datos



Se crean las entidades, registramos los accesorios, ahora queda por completar todo en la base de datos, los siguientes pasos que llevo a cabo con cada cambio de entidades o accesorios




php bin/console doctrine:schema:drop --full-database --force #  ,   

php bin/console doctrine:migrations:diff #   .       !

php bin/console doctrine:migrations:migrate #     
php bin/console doctrine:fixtures:load #     


Paso 6: crear autorización



En la consola escribimos



php bin/console make:auth


Establecemos la configuración y los nombres de la siguiente manera:




# php bin/console make:auth

 What style of authentication do you want? [Empty authenticator]:
  [0] Empty authenticator
  [1] Login form authenticator
 > 1

 The class name of the authenticator to create (e.g. AppCustomAuthenticator):
 > UserAuthenticator

 Choose a name for the controller class (e.g. SecurityController) [SecurityController]:
 > UserAuthSecurityController

 Do you want to generate a '/logout' URL? (yes/no) [yes]:
 >

 created: src/Security/UserAuthenticator.php
 updated: config/packages/security.yaml
 created: src/Controller/UserAuthSecurityController.php
 created: templates/security/login.html.twig

  Success!

 Next:
 - Customize your new authenticator.
 - Finish the redirect "TODO" in the App\Security\UserAuthenticator::onAuthenticationSuccess() method.
 - Review & adapt the login template: templates/security/login.html.twig.


Como resultado, security.yaml se actualizará y se crearán 3 archivos



Paso 7 - edite security.yaml



Después de crear la autorización, security.yaml se ve así:




security:
    encoders:
        App\Entity\User:
            algorithm: auto
        App\Entity\Admin:
            algorithm: auto


    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\Admin
                property: email
        # used to reload user from session & other features (e.g. switch_user)
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: true
            lazy: true
            provider: app_user_provider
            guard:
                authenticators:
                    - App\Security\UserAuthenticator
            logout:
                path: app_logout
                # where to redirect after logout
                # target: app_any_route

            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#firewalls-authentication

            # https://symfony.com/doc/current/security/impersonating_user.html
            # switch_user: true

    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        # - { path: ^/admin, roles: ROLE_ADMIN }
        # - { path: ^/profile, roles: ROLE_USER }



Necesitamos agregar un nuevo proveedor admin_user_provider y cambiar la configuración de los firewalls .



Finalmente , el archivo security.yaml debería verse así:




security:
    encoders:
        App\Entity\User:
            algorithm: auto
        App\Entity\Admin:
            algorithm: auto


    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
        app_admin_provider:
            entity:
                class: App\Entity\Admin
                property: email
        # used to reload user from session & other features (e.g. switch_user)
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        admin_secured_area:
            pattern:   ^/admin
            anonymous: ~
            provider: app_admin_provider
            form_login:
                login_path: /admin/login
                check_path: /admin/login_check
                default_target_path: /admin/login
                username_parameter: email
                password_parameter: password
            guard:
                authenticators:
                    - App\Security\AdminAuthenticator
            logout:
                path: app_logout
                # where to redirect after logout
                target: /admin/login

        user_secured_area:
            pattern:   ^/
            anonymous: ~
            provider: app_user_provider
            form_login:
                login_path: /login
                check_path: /login_check
                default_target_path: /login
                username_parameter: email
                password_parameter: password
            logout:
                path: app_logout
                # where to redirect after logout
                target: /login
                
    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        # - { path: ^/admin, roles: ROLE_ADMIN }
        # - { path: ^/profile, roles: ROLE_USER }



Paso 8: cambie el nombre de la plantilla login.html.twig



Esto debe hacerse, ya que volveremos a crear la autorización a través de make: auth.

Pongamos nombre a este archivo.



Paso 9: edición del UserAuthSecurityController



El archivo se encuentra en la ruta App \ Controller, ya que cambiamos el nombre de la plantilla, esto debe cambiarse en el controlador.



Cuál debería ser el controlador:




<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class UserAuthSecurityController extends AbstractController
{
    /**
     * @Route("/login", name="app_login")
     */
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        // if ($this->getUser()) {
        //     return $this->redirectToRoute('target_path');
        // }

        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();
        // last username entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render('security/user-login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
    }

    /**
     * @Route("/logout", name="app_logout")
     */
    public function logout()
    {
        throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
    }
}


Paso 10: crear una segunda autorización



En la consola, escribe:




php bin/console make:auth


Como hemos agregado un nuevo app_admin_provider , se nos pedirá que elijamos qué firewall queremos actualizar:



imagen



Después de seleccionar el firewall, ofrezca seleccionar Entity, seleccione \ App \ Entity \ Admin:



imagen



Paso 11: cambie el nombre de login.html.twig que acabamos de crear



Cambie el nombre del login.html.twig recién creado a admin-login.html.twig



Paso 12 - editando el AdminAuthController que acabamos de crear



Cambiar ruta y nombre de plantilla:




<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class AdminAuthController extends AbstractController
{
    /**
     * @Route("/admin/login", name="app_admin_login")
     */
    public function adminLogin(AuthenticationUtils $authenticationUtils): Response
    {
        // if ($this->getUser()) {
        //     return $this->redirectToRoute('target_path');
        // }

        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();
        // last username entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render('security/admin-login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
    }

    /**
     * @Route("/logout", name="app_logout")
     */
    public function logout()
    {
       throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
    }
}


Paso 13: edición del archivo config / routes.yaml



Cree login_check y admin_login_check, que definimos en la configuración del firewall en el



archivo config / packages / security.yaml Cómo debería verse el archivo config / routes.yaml:




#index:
#    path: /
#    controller: App\Controller\DefaultController::index
login_check:
  path: /login_check
admin_login_check:
  path: /admin/login_check



Paso 14: edite el archivo templates / secutiry / user-login.html.twig



Agregue el atributo de acción a la etiqueta:



{% extends 'base.html.twig' %}

{% block title %}Log in!{% endblock %}

{% block body %}
<form action="{{ path('login_check') }}" method="post">
    {% if error %}
        <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
    {% endif %}

    {% if app.user %}
        <div class="mb-3">
            You are logged in as {{ app.user.username }}, <a href="{{ path('app_logout') }}">Logout</a>
        </div>
    {% endif %}

    <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
    <label for="inputEmail">Email</label>
    <input type="email" value="{{ last_username }}" name="email" id="inputEmail" class="form-control" required autofocus>
    <label for="inputPassword">Password</label>
    <input type="password" name="password" id="inputPassword" class="form-control" required>

    <input type="hidden" name="_csrf_token"
           value="{{ csrf_token('authenticate') }}"
    >

    {#
        Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
        See https://symfony.com/doc/current/security/remember_me.html

        <div class="checkbox mb-3">
            <label>
                <input type="checkbox" name="_remember_me"> Remember me
            </label>
        </div>
    #}

    <button class="btn btn-lg btn-primary" type="submit">
        Sign in
    </button>
</form>
{% endblock %}



Paso 15: edite el archivo templates / secutiry / admin-login.html.twig



Agregue el atributo de acción a la etiqueta:



{% extends 'base.html.twig' %}

{% block title %}Log in!{% endblock %}

{% block body %}
<form action="{{ path('admin_login_check') }}" method="post">
    {% if error %}
        <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
    {% endif %}

    {% if app.user %}
        <div class="mb-3">
            You are logged in as {{ app.user.username }}, <a href="{{ path('app_logout') }}">Logout</a>
        </div>
    {% endif %}

    <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
    <label for="inputEmail">Email</label>
    <input type="email" value="{{ last_username }}" name="email" id="inputEmail" class="form-control" required autofocus>
    <label for="inputPassword">Password</label>
    <input type="password" name="password" id="inputPassword" class="form-control" required>

    <input type="hidden" name="_csrf_token"
           value="{{ csrf_token('authenticate') }}"
    >

    {#
        Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
        See https://symfony.com/doc/current/security/remember_me.html

        <div class="checkbox mb-3">
            <label>
                <input type="checkbox" name="_remember_me"> Remember me
            </label>
        </div>
    #}

    <button class="btn btn-lg btn-primary" type="submit">
        Sign in
    </button>
</form>
{% endblock %}



Paso 16: lanzamiento del sitio web



Para iniciar el sitio, primero instale el paquete de servidor web:



composer require symfony/web-server-bundle --dev ^4.4.2


Lanzamos el sitio:



php bin/console server:run


Paso 17: prueba de autorización para el usuario



Vaya a la página 127.0.0.1 : 8000 / login



Vemos esto: Iniciamos



imagen



sesión con el correo electrónico user@example.com y la contraseña 123654.



Vemos que la autorización fue exitosa:



imagen



si usa datos incorrectos, obtendrá el error de Credenciales no válidas.



Paso 18 - prueba de autorización para administrador



Vamos a la página 127.0.0.1 : 8000 / admin / login.



Vemos esto:



imagen



Inicie sesión con el correo electrónico admin@example.com y la contraseña 123654.



Parece que todo es exitoso:



imagen



si ingresa datos incorrectos o si ingresamos datos del Usuario en la página / admin / inicio de sesión: se producirá un error de credenciales no válidas. Para la página / login, lo mismo - ingrese los datos del administrador - habrá un error.



Conclusión



Gracias a todos los que leyeron hasta el final, trataron de escribir la guía con el mayor detalle posible, para que todos, si fuera necesario, pudieran hacer algo similar.



Decidí escribir un tutorial después de no poder encontrar instrucciones detalladas para esta tarea en la documentación, guías o debates en inglés, sin mencionar los materiales en ruso.



All Articles