人人都能看懂的全栈开发教程——Symfony 用户登录

人人都能看懂的全栈开发教程——Symfony 用户登录

Chris Yue No Comment
Posts

上一篇我们从老项目里迁移命令到新项目,并实现了在新项目里通过命令行创建用户,这一章我们来解决新项目用户登录的问题。

Symfony 依然提供命令来生成与登录相关的代码和配置,运行下面命令即可:

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

专门用于处理登录逻辑的类名,这里就叫 LoginFormAuthenticator 好了。

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

类似我们老项目里专门创建了一个单独的控制器文件处理登录,Symfony 框架也一样,这里问的是我们专门处理登录的控制器的类名打算叫什么。我们采纳中括号里的建议,直接回车即可。

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

这里是问要不要自动生成用于退出的 /logout 路径。虽然没有需求说让用户退出登录,但我们可以生成一下,看看 Symfony 做了什么。

最后,命令会提示创建了登录相关的控制器文件,模板文件,以及专门处理登录的 LoginFormAuthenticator 类文件,并且还更新了 config/packages/security.yaml 配置文件。

执行完命令后,这里说说 Symfony 是如何通过配置文件来管理哪些路径才能登录的。我们先更新 config/packages/security.yaml 文件:

security:
    ...
    # firewalls 即『防火墙』,用户是否能访问一个地址就是由这些防火墙规则来限制的
    firewalls:
        # 这是 Symfony 自动生成的规则,dev 是规则名称
        dev:
            # pattern 是访问路径的正则表达式
            # 大家如果不熟悉正则表达式没有关系,总之这里表示的是
            # 如果路径是 /_profiler,/_wdt,/css,/images,/js 开头,就不需要做任何检查。
            # 之前有跟大家说过 Symfony 页面下方的调试条是可以点进去看的,打开的调试信息页面地址就是 _profiler 开头的
            # 这些开发调试用的信息还要求登录才能访问就有点不合理了
            # 另外 /css 这些也都是不登录就应该能访问的静态文件,
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            # 这句表示对于上面的路径,不需要做任何的检查
            security: false
        # 下面部分是刚才执行的命令帮我们生成的
        main:
            # 此配置的意思是,如果用户没登录,就当成『匿名访问』
            # 匿名访问的用户是没有角色的
            # 还记得上一篇 UserInterface 要求的 getRoles 方法吗?那就是返回用户角色的方法
            # 并且我们返回了一个 ROLE_USER,表示登录用户角色就是 ROLE_USER
            anonymous: lazy
            guard:
                # 这种 - 开头的在 yaml 里表示数组中的一项
                # 可以看得出来可同时支持不止一种登录方式
                authenticators:
                    - App\Security\LoginFormAuthenticator
            logout:
                path: app_logout
    access_control:
        # 将例子删掉,我们添加自己的规则:除了登录地址,其他地址都需要用户是 ROLE_USER 角色才能访问
        - { path: ^/(?!login), roles: ROLE_USER }

只有允许了 /login 地址可以匿名访问,我们才能正常访问登录页面,否则会因为 /login 页面无权访问,而引导浏览器继续跳转到 /login 地址尝试登录所造成的的无限跳转问题。

接下来我们还需要修改 src/Security/LoginFormAuthenticator.php。我们先打开这个文件,大家不要被这解决 100 行的代码吓到了,我们现在不需要关注那么多。到目前为止,读者只需要将下面的改变添加到代码里即可:

<?php

namespace App\Security;

// 需要将我们检查密码是否正确的 PasswordEncoder 接口引入
use Domain\PasswordEncoderInterface;
...

class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
    ...
    // 这里没有依赖有 login 方法的 UserManager,而是 PasswordEncoderInterface
    // 原因是什么请看后面的注释
    private $passwordEncoder;

    public function __construct(
        EntityManagerInterface $entityManager,
        UrlGeneratorInterface $urlGenerator,
        CsrfTokenManagerInterface $csrfTokenManager,
        PasswordEncoderInterface $passwordEncoder
    ) {
        ...
        $this->passwordEncoder = $passwordEncoder;
    }

    ...

    // Symfony 生成的 Authenticator 把一个登录行为抽象成了几个步骤,
    // 第一个步骤是通过某个唯一字段获取用户,还记得上一章创建用户时有问我们
    // 用户的唯一标识是什么,当我们回答了 username 之后,Symfony 框架就知道
    // 怎么获取用户对象了
    // 而这个方法则是对应了第二个步骤,检查用户的密码是否正确,正好对应我们
    // PasswordEncoderInterfae 的 verify 功能
    public function checkCredentials($credentials, UserInterface $user)
    {
        // $credentials 里的 password 即是用户登录时输入的明文密码
        return $this->passwordEncoder->verify($credentials['password'], $user->getPassword());
    }

    // 因为 UserManager::login 方法实际上是把找用户和检查密码是否正确两个步骤放在一起,所以这里也用不上了
}

完成这一步,我们就可以通过登录页面正常登录了。另外 Symfony 还做了许多我们需求外的功能,比如返回登录出错的错误信息显示等,大家可以试试看。

因为我们已经可以正常使用登录功能,我们再将之前用来做测试的首页代码做修改,让首页返回当前登录用户关联的任务:

<?php

...

class TaskController extends AbstractController
{
    /**
     * @Route("/", methods={"GET"})
     */
    public function index()
    {
        // 控制器的 getUser 方法可返回当前用户的 App\Entity\User 对象
        $user = $this->getUser();
        $tasks = $this->getDoctrine()->getRepository(Task::class)->findByUser($user);


        return $this->render('task/index.html.twig', [
            'tasks' => $tasks,
        ]);
    }
}

让我们再来将 templates/security/login.html.twig 文件修改成我们之前的设计:

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

{% block title %}登录{% endblock %}

{% block body %}
<form method="post">
    <input type="text" value="{{ last_username }}" name="username" id="inputUsername" class="border input" required autofocus>
    <input type="password" name="password" id="inputPassword" class="border input" required>

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

    <input type="submit" value="提交" class="border btn">
</form>
{% endblock %}

需要注意的是,我们的表单项除了用户名,密码输入框以及提交按钮之外,还多了一个 name 是 _csrf_token 的隐藏项(type="hidden" 即隐藏项,用于不让用户看见,但要将某个值随表单一起提交时使用)。这个隐藏项是不能删除的。如果你仔细看过 LoginFormAuthenticatorgetUser 方法,可以发现 _csrf_token 值是会被检查的。表单里添加这个字段,其实是跟安全有关系的,是为了防止另外一个大名鼎鼎的『CSRF 攻击』。关于这个话题本篇就不多说了,读者可以看看这篇文章

在之前我们要求生成 /logout 地址用来退出登录,如果大家已经在登录状态,可以先以手动在地址栏输入的方式访问此地址来退出登录。

本篇文章完整代码见这里

人人都能看懂的全栈开发教程——Symfony 用户登录 by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

写作累,服务器还越来越贵
求分担,祝愿好人一生平安
天使打赏人

发表评论

96 − = 93