本文概述
在上一篇文章中, 我们向你展示了如何创建注册表单以在应用程序中添加新用户。显然, 如果用户已经在你的应用程序上拥有一个帐户, 则他们需要登录到该应用程序, 因此他在访问你的网站时将拥有一个会话和凭据。
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
编码愉快!
评论前必须登录!
注册