步骤 15: 保护管理后台

管理后台的界面应该只能让信任的人来访问。可以用 Symfony 的 Security 组件把网站的这个区域保护起来。

和 Twig 一样,Security 组件作为传递性依赖已经安装好了。我们来将它显式地加入项目的 composer.json 文件:

  1. $ symfony composer req security

定义 User 实体

虽然参会人员不能在网站上创建他们自己的账号,但我们要为管理员开发一套完备的认证系统。所以我们只会有一个用户,那就是网站管理员。

第一步是定义 User 实体类。为了避免混淆,我们把它命名为 Admin

为了把 Admin 实体整合到 Security 组件的认证系统,该实体需要满足一些条件。比如,它需要一个 password 属性。

使用量身定做的 make:user 命令来创建 Admin 实体,而不是用传统的 make:entity 命令:

  1. $ symfony console make:user Admin

回答命令行交互模式下的问题:我们想要用 Doctrine 来存储管理员(选择 yes),使用 username 属性作为管理员的独一显示名,每个用户需要有密码(选择 yes)。

命令生成的类文件里包含的方法有 getRoles()eraseCredentials() 以及其它一些,这些方法都会被 Symfony 的认证系统调用。

如果你要 Admin 类里增加更多属性,请用 make:entity

让我们增加一个 __toString() 方法,因为 EasyAdmin 会用到它:

  1. --- a/src/Entity/Admin.php
  2. +++ b/src/Entity/Admin.php
  3. @@ -75,6 +75,11 @@ class Admin implements UserInterface
  4. return $this;
  5. }
  6. + public function __toString(): string
  7. + {
  8. + return $this->username;
  9. + }
  10. +
  11. /**
  12. * @see UserInterface
  13. */

这个命令除了生成 Admin 实体,它也更新了安全配置文件,将这个实体类接入到认证系统:

  1. --- a/config/packages/security.yaml
  2. +++ b/config/packages/security.yaml
  3. @@ -1,7 +1,15 @@
  4. security:
  5. + encoders:
  6. + App\Entity\Admin:
  7. + algorithm: auto
  8. +
  9. # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
  10. providers:
  11. - in_memory: { memory: null }
  12. + # used to reload user from session & other features (e.g. switch_user)
  13. + app_user_provider:
  14. + entity:
  15. + class: App\Entity\Admin
  16. + property: username
  17. firewalls:
  18. dev:
  19. pattern: ^/(_(profiler|wdt)|css|images|js)/

对密码明文进行加密有多种可能的算法,我们让 Symfony 来选择最优的算法(这个选择会随时间改变)。

是时候生成一个数据库结构迁移文件,并且更新数据库结构了:

  1. $ symfony console make:migration
  2. $ symfony console doctrine:migrations:migrate -n

为管理员生成一个密码

我们不会去开发一个专用的系统用于管理员的账号创建。再说一遍,我们这里只会有一个管理员。它的账号名就叫 admin,并且我们需要对密码进行加密。

选一个你想要的密码,然后运行以下的命令来生成一个加密后的密码:

  1. $ symfony console security:encode-password
  1. Symfony Password Encoder Utility
  2. ================================
  3. Type in your password to be encoded:
  4. >
  5. ------------------ ---------------------------------------------------------------------------------------------------
  6. Key Value
  7. ------------------ ---------------------------------------------------------------------------------------------------
  8. Encoder used Symfony\Component\Security\Core\Encoder\MigratingPasswordEncoder
  9. Encoded password $argon2id$v=19$m=65536,t=4,p=1$BQG+jovPcunctc30xG5PxQ$TiGbx451NKdo+g9vLtfkMy4KjASKSOcnNxjij4gTX1s
  10. ------------------ ---------------------------------------------------------------------------------------------------
  11. ! [NOTE] Self-salting encoder used: the encoder generated its own built-in salt.
  12. [OK] Password encoding succeeded

创建一个管理员

用 SQL 语句插入一个管理员用户:

  1. $ symfony run psql -c "INSERT INTO admin (id, username, roles, password) \
  2. VALUES (nextval('admin_id_seq'), 'admin', '[\"ROLE_ADMIN\"]', \
  3. '\$argon2id\$v=19\$m=65536,t=4,p=1\$BQG+jovPcunctc30xG5PxQ\$TiGbx451NKdo+g9vLtfkMy4KjASKSOcnNxjij4gTX1s')"

请注意密码那一列里,我们对 $ 符号进行了转义;对每个 $ 都进行转义!

配置认证系统

现在我们既然有了管理员用户,就可以去保护起后台了。Symfony 支持几种认证策略。让我们用经典而且流行的 表单认证系统

运行 make:auth 命令来更新安全方面的配置,生成一个登录页模板,并且创建一个 认证器

  1. $ symfony console make:auth

选择 1 来生成一个登录表单认证器,将这个认证器的类命名为 AppAuthenticator,将控制器类命名为 SecurityController,并且生成一个 /logout 路径(选择 yes)。

这个命令会更新安全配置,将生成的类接入认证系统:

  1. --- a/config/packages/security.yaml
  2. +++ b/config/packages/security.yaml
  3. @@ -16,6 +16,13 @@ security:
  4. security: false
  5. main:
  6. anonymous: lazy
  7. + guard:
  8. + authenticators:
  9. + - App\Security\AppAuthenticator
  10. + logout:
  11. + path: app_logout
  12. + # where to redirect after logout
  13. + # target: app_any_route
  14. # activate different ways to authenticate
  15. # https://symfony.com/doc/current/security.html#firewalls-authentication

按照命令输出的提示,我们需要在 onAuthenticationSuccess() 方法中设置一个定制路径,它是用户登录成功后要跳转的路径:

  1. --- a/src/Security/AppAuthenticator.php
  2. +++ b/src/Security/AppAuthenticator.php
  3. @@ -96,8 +96,7 @@ class AppAuthenticator extends AbstractFormLoginAuthenticator implements Passwor
  4. return new RedirectResponse($targetPath);
  5. }
  6. - // For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
  7. - throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
  8. + return new RedirectResponse($this->urlGenerator->generate('admin'));
  9. }
  10. protected function getLoginUrl()

小技巧

我是如何记住 EasyAdmin 路由的名字叫 admin 的(正如在 App\Controller\Admin\DashboardController 里所配置的)?其实我并不记得。你可以看一下控制器文件,但你可以运行下面这个命令,它会展示路由名和路径之间的关联:

  1. $ symfony console debug:router

增加授权访问控制的规则

一个安全系统由两部分组成:认证授权。当创建一个管理员时,我们给了它 ROLE_ADMIN 的角色。让我们来限定 /admin 路径下的区域只能允许拥有该角色的用户才能访问,我们是通过在 access_control 下增加一条规则来实现的:

  1. --- a/config/packages/security.yaml
  2. +++ b/config/packages/security.yaml
  3. @@ -35,5 +35,5 @@ security:
  4. # Easy way to control access for large sections of your site
  5. # Note: Only the *first* access control that matches will be used
  6. access_control:
  7. - # - { path: ^/admin, roles: ROLE_ADMIN }
  8. + - { path: ^/admin, roles: ROLE_ADMIN }
  9. # - { path: ^/profile, roles: ROLE_USER }

access_control 下的规则通过正则表达式来限制访问。当用户尝试访问的 URL 以 /admin 开头时,安全系统会检查这个登录的用户是否有 ROLE_ADMIN 这个角色。

通过登录表单认证

现在如果你试着进入后台,你会被重定向到登录页面,并被要求录入账户名和密码:

步骤 15: 保护管理后台 - 图1

账户名是 admin,密码就是你之前编码的明文密码。如果你不做修改地复制了我的 SQL 命令,那么密码就是 admin

注意,EasyAdmin 自动识别出了 Symfony 的认证系统:

步骤 15: 保护管理后台 - 图2

试着点击“退出”链接。完成了!后台被充分地保护起来了。

注解

如果想要一个功能完备的表单认证系统,去看一下 make:registration-form 命令。

深入学习


This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.