个性化阅读
专注于IT技术分析

如何在Symfony 4.3中实现自己的用户身份验证系统:第3部分(创建登录表单和注销路径)

本文概述

在上一篇文章中, 我们向你展示了如何创建注册表单以在应用程序中添加新用户。显然, 如果用户已经在你的应用程序上拥有一个帐户, 则他们需要登录到该应用程序, 因此他在访问你的网站时将拥有一个会话和凭据。

Symfony 4非常容易实现, 我们将在短期内向你说明如何创建登录和注销路径:

1.创建登录路径

最初, 我们需要创建一条路径, 用户可以在其中访问登录表单。在应用程序的控制器目录(/ src / Controller /)中创建具有以下内容的SecurityController.php文件:

<?php

// src/Controller/SecurityController.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 SecurityController extends AbstractController
{
    /**
     * @Route("/login", name="app_login")
     */
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        // Get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();
        
        // Retrive the last email entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();

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

通过依赖项注入, AuthenticationUtils实例将通过登录对象作为第一个参数接收该对象, 你可以从该对象中获取示例信息, 最后的身份验证错误以及表单上提供的用户名。

2.创建登录表单身份验证器

接下来, 你将需要创建扩展了AbstractFormLoginAuthenticator基类的authenticator类, 这将使表单登录身份验证更加容易。此类将在构造函数中接收此模块所需的4个关键组件, 即实体管理器(用于创建查询), 路由器接口(用于创建路由), CSRF令牌管理器(检查表单是否有效)和密码编码器(以检查身份验证是否有效)。

当supports方法返回true时, 将执行此身份验证器(在步骤3中注册后)。在这种情况下, 当当前路由为app_login且方法为POST(提交表单时)时, 它将被触发:

<?php

// src/Security/LoginFormAuthenticator.php
namespace App\Security;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
    use TargetPathTrait;

    private $entityManager;
    private $router;
    private $csrfTokenManager;
    private $passwordEncoder;

    public function __construct(EntityManagerInterface $entityManager, RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->entityManager = $entityManager;
        $this->router = $router;
        $this->csrfTokenManager = $csrfTokenManager;
        $this->passwordEncoder = $passwordEncoder;
    }

    public function supports(Request $request)
    {
        return 'app_login' === $request->attributes->get('_route')
            && $request->isMethod('POST');
    }

    public function getCredentials(Request $request)
    {
        $credentials = [
            'email' => $request->request->get('email'), 'password' => $request->request->get('password'), 'csrf_token' => $request->request->get('_csrf_token'), ];
        $request->getSession()->set(
            Security::LAST_USERNAME, $credentials['email']
        );

        return $credentials;
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
        if (!$this->csrfTokenManager->isTokenValid($token)) {
            throw new InvalidCsrfTokenException();
        }

        $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);

        if (!$user) {
            // fail authentication with a custom error
            throw new CustomUserMessageAuthenticationException('Email could not be found.');
        }

        return $user;
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }

        // For example : return new RedirectResponse($this->router->generate('some_route'));
        throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
    }

    protected function getLoginUrl()
    {
        return $this->router->generate('app_login');
    }
}

请注意, 你需要在此类中处理, 特别是在onAuthenticationSuccess回调上, 现在用户会发生什么。通常, 你应该将他重定向到应用程序的索引页面或其他内容, 因此请确保根据你的需要修改回调的代码, 例如, 不要引发异常, 而只需重定向到我们的索引路由即可:

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
    if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
        return new RedirectResponse($targetPath);
    }

    //  Redirect user to homepage
    return new RedirectResponse($this->router->generate('app_index'));
}

3.注册Guard Authenticator

身份验证器已经存在, 但是你需要在主防火墙中注册它:

# app/config/packages/security.yaml
security:
    firewalls:
        main:
            anonymous: true
            guard:
                authenticators:
                    - App\Security\LoginFormAuthenticator

    # 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: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }

这样做, 该类将被注册, 并在该类的supports方法返回true时作出反应。

4.创建登录视图

最后但同样重要的是, 在登录路径中, 我们呈现了一个Twig视图, 即login.html.twig, 尚未创建, 并将包含以下标记(存储在/ app / templates / security /中):

{# /app/templates/security/login.html.twig #}
{% extends 'base.html.twig' %}

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

{% block body %}
<form method="post">
    {# If there's any error, display it to the user #}
    {% if error %}
        <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
    {% endif %}

    <h1>Please sign in</h1>
    
    {# Email Input #}
    <label for="inputEmail" class="sr-only">Email</label>
    <input type="email" value="{{ last_username }}" name="email" id="inputEmail" class="form-control" placeholder="Email" required autofocus>
    
    {# Password Input #}
    <label for="inputPassword" class="sr-only">Password</label>
    <input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
    
    {# CSRF Token Input #}
    <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}" />

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

5.访问和登录

创建视图后, 如果你尝试访问路线mywebsite.com/login, 则会看到登录表单:

登录表格

提供通过本文第2部分中创建的注册表单注册的用户的一些凭据。如果你在其中指定了重定向路由, 则所有操作都会正常进行, 并且你将被重定向到该路由, 并且你会在开发工具中看到已通过身份验证的信息:

用户验证表

这就是登录部分!

6.创建注销路径

现在, 你的用户如果登录也应该能够关闭会话, 因此你需要公开注销路径。你可以在还创建了登录路由的同一SecurityController中创建它, 因此可以添加它, 这是一个简单的空操作, 名为logout:

<?php

// src/Controller/SecurityController.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 SecurityController extends AbstractController
{
    /**
     * @Route("/logout", name="app_logout")
     */
    public function logout(): Response
    {
        // controller can be blank: it will never be executed!
    }
}

请注意, 该路由将具有在下一步中使用的标识符, 以启用身份验证系统的注销功能。

7.启用注销

现在已经存在注销路由, 你需要在security.yaml文件的主防火墙中指定它:

# app/config/packages/security.yaml
security:
    firewalls:
        main:
            logout:
                path: app_logout
                # where to redirect after logout ?? You can specify the target property
                # target: app_any_route 

你还可以在此块中指定注销后将用户重定向到的位置。注册此路由后, 登录后访问路由mywebsite.com/logout, 该会话将被删除, 用户将需要再次登录该应用程序。

阅读教程的所有部分”如何在SYMFONY 4.3中实施你自己的用户认证系统”

  • 第1部分:创建自定义用户类。
  • 第2部分:创建用户注册表单。
  • 第3部分:创建登录表单和注销路径。

对本教程的兴趣链接

  • https://symfony.com/doc/current/security.html

编码愉快!

赞(0)
未经允许不得转载:srcmini » 如何在Symfony 4.3中实现自己的用户身份验证系统:第3部分(创建登录表单和注销路径)

评论 抢沙发

评论前必须登录!