[Symfony 5] Autorização separada para administradores e usuários com duas entidades diferentes e formulários de login

objetivo



Crie uma autorização separada no Symfony 5:



  • Administrador - terá a entidade Admin , faça o login url / admin / login
  • Usuário - terá uma entidade de usuário , URL de login / login
  • Os dados de login não devem se sobrepor, não podemos fazer login como usuário na página / admin / login
  • Duas entidades diferentes devem ser criadas
  • Dois controladores diferentes devem ser criados para o login e dois Security diferentes
  • Possibilidade de configurar o encaminhamento após autorização separadamente um do outro
  • A capacidade de usar diferentes dados de autorização (por exemplo, para o usuário, queremos que os usuários digitem seu email / senha e para o administrador fornecer proteção adicional adicionando alguns recursos de Uuid


Por que este guia é necessário?



Minha tarefa foi dividir o formulário de login com a entidade Usuário em dois diferentes - para o usuário (usuário da entidade) e para o administrador (administrador da entidade) para a funcionalidade normal do painel de administração (neste caso, EasyAdmin).



Neste tutorial, descreverei o caminho inteiro passo a passo, começando com a instalação da estrutura em si e terminando com a criação de duas formas diferentes de autorização.



Especificações



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


O tutorial está atualizado no final de junho de 2020.



Etapa 0 - Instale o Symfony 5



Assumiremos que você instalou todos os componentes necessários, incluindo o Composer, no diretório raiz do OpenServer (... / domínios).



composer create-project symfony/website-skeleton auth_project




Etapa 1 - configurando o banco de dados



Crie um novo banco de dados, denomine auth_project, deixe a senha e o usuário serem mysql. Agora precisamos redefinir as configurações .env.



Deve ser assim:




# 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 ###



Etapa 2 - criando a entidade Usuário



Crie uma entidade Usuário, selecione o email como um valor exclusivo



php bin/console make:user


imagem



Etapa 3 - Criar uma entidade administrativa



Repetimos tudo o que é descrito na etapa anterior, em vez do nome da entidade Usuário, coloque Admin



Etapa 4 - prepare os jogos



Criaremos duas contas de teste, uma para Usuário e a segunda para Admin. Vamos usar o DoctrineFixturesBundle



Primeiro, você precisa colocá-lo



composer require --dev orm-fixtures


Após a instalação em / src, a pasta DataFixtures será exibida, na qual o arquivo AppFixtures.php já será criado



, renomeie-o para UserFixtures.php e adicione a funcionalidade necessária.



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


O mesmo deve ser feito para o administrador - create 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();
    }
}


Etapa 5 - faça o upload das migrações e acessórios para o banco de dados



As entidades foram criadas, registramos equipamentos, resta agora preencher tudo no banco de dados, as seguintes ações que realizo com cada alteração de entidades ou equipamentos




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 #     


Etapa 6 - criar autorização



No console, prescrevemos



php bin/console make:auth


Definimos as configurações e nomes da seguinte maneira:




# 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 será atualizado e 3 arquivos serão criados



Etapa 7 - editar security.yaml



Após a criação da autorização, security.yaml fica assim:




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 }



Precisamos adicionar um novo provedor admin_user_provider e alterar as configurações dos firewalls .



Por fim , o arquivo security.yaml deve ficar assim:




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 }



Etapa 8 - renomeie o modelo login.html.twig



Isso precisa ser feito, pois recriaremos a autorização via make: auth.

Vamos nomear esse arquivo.



Etapa 9 - editar UserAuthSecurityController



O arquivo está localizado no caminho App \ Controller, já que alteramos o nome do modelo, isso deve ser alterado no controlador.



Qual deve ser o 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.');
    }
}


Etapa 10 - crie uma segunda autorização



No console, escreva:




php bin/console make:auth


Como adicionamos um novo app_admin_provider , será solicitado que você escolha qual firewall deseja atualizar:



imagem



Depois de escolher um firewall, ofereça para selecionar Entity, selecione \ App \ Entity \ Admin:



imagem



Etapa 11 - renomeie o login.html.twig que acabamos de criar



Renomeie o recém-criado login.html.twig para admin-login.html.twig



Etapa 12 - editando o AdminAuthController que acabamos de criar



Alterar rota e nome do modelo:




<?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.');
    }
}


Etapa 13 - edite o arquivo config / routes.yaml



Crie login_check e admin_login_check, que definimos nas configurações do firewall no arquivo config / packages / security.yaml



Como o arquivo config / routes.yaml deve se parecer:




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



Etapa 14 - edite o arquivo templates / secutiry / user-login.html.twig



Adicione o atributo action à tag:



{% 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 %}



Etapa 15 - edite o arquivo templates / secutiry / admin-login.html.twig



Adicione o atributo action à tag:



{% 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 %}



Etapa 16 - iniciando o site



Para iniciar o site, primeiro defina web-server-bundle:



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


Lançamos o site:



php bin/console server:run


Etapa 17 - testando a autorização para o usuário



Vá para a página 127.0.0.1 : 8000 / login.



Vemos o seguinte:



imagem



Efetuamos login usando o email user@example.com e a senha 123654.



Vemos que a autorização foi bem-sucedida:



imagem



Se você usar dados incorretos, ele eliminará o erro de credenciais inválidas.



Etapa 18 - testando a autorização para Admin



Vá para a página 127.0.0.1 : 8000 / admin / login.



Vemos o seguinte:



imagem



Efetuamos login usando o email admin@example.com e a senha 123654.



Aparentemente, tudo foi bem-sucedido:



imagem



se inserirmos dados incorretos ou se entrarmos em Usuário na página / admin / login - será gerado um erro nas credenciais inválidas. Para a página / login, a mesma coisa - insira os dados do Admin - haverá um erro.



Conclusão



Obrigado a todos que leram até o final, tentaram pintar o guia o mais detalhadamente possível, para que todos, se necessário, pudessem fazer algo semelhante.



Decidi escrever um tutorial depois de não encontrar instruções detalhadas para esta tarefa na documentação, guias ou discussões em inglês, sem mencionar os materiais em russo.



All Articles