跳到内容

如何创建和启用自定义用户检查器

编辑此页

在用户身份验证期间,可能需要额外的检查来验证已识别的用户是否允许登录。通过定义自定义用户检查器,你可以为每个防火墙定义应使用的检查器。

创建自定义用户检查器

用户检查器是必须实现 UserCheckerInterface 接口的类。此接口定义了两个方法,分别称为 checkPreAuth()checkPostAuth(),用于在用户身份验证之前和之后执行检查。如果一个或多个条件未满足,则抛出一个异常,该异常扩展了 AccountStatusException 类。考虑使用 CustomUserMessageAccountStatusException,它扩展了 AccountStatusException,并允许自定义向用户显示错误消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
namespace App\Security;

use App\Entity\User as AppUser;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AccountExpiredException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class UserChecker implements UserCheckerInterface
{
    public function checkPreAuth(UserInterface $user): void
    {
        if (!$user instanceof AppUser) {
            return;
        }

        if ($user->isDeleted()) {
            // the message passed to this exception is meant to be displayed to the user
            throw new CustomUserMessageAccountStatusException('Your user account no longer exists.');
        }
    }

    public function checkPostAuth(UserInterface $user, TokenInterface $token): void
    {
        if (!$user instanceof AppUser) {
            return;
        }

        // user account is expired, the user may be notified
        if ($user->isExpired()) {
            throw new AccountExpiredException('...');
        }

        if (!\in_array('foo', $token->getRoleNames())) {
            throw new AccessDeniedException('...');
        }
    }
}

7.2

checkPostAuth() 方法的 token 参数在 Symfony 7.2 中引入。

启用自定义用户检查器

接下来,确保你的用户检查器注册为服务。如果你正在使用 默认的 services.yaml 配置,则该服务将自动注册。

剩下要做的就是将检查器添加到所需的防火墙,其中值是你的用户检查器的服务 ID

1
2
3
4
5
6
7
8
9
# config/packages/security.yaml

# ...
security:
    firewalls:
        main:
            pattern: ^/
            user_checker: App\Security\UserChecker
            # ...

使用多个用户检查器

应用程序通常具有多个身份验证入口点(例如传统的基于表单的登录和 API),这些入口点可能对每个入口点具有唯一的检查器规则,以及对所有入口点具有通用规则。为了允许在防火墙上使用多个用户检查器,为每个防火墙创建了 ChainUserChecker 类的服务。

要使用链式用户检查器,首先你需要使用 security.user_checker.<firewall> 标签标记你的用户检查器服务(其中 <firewall> 是你安全配置中防火墙的名称)。服务标签还支持 priority 属性,允许你定义调用用户检查器的顺序

1
2
3
4
5
6
7
8
9
10
11
12
# config/services.yaml

# ...
services:
    App\Security\AccountEnabledUserChecker:
        tags:
            - { name: security.user_checker.api, priority: 10 }
            - { name: security.user_checker.main, priority: 10 }

    App\Security\APIAccessAllowedUserChecker:
        tags:
            - { name: security.user_checker.api, priority: 5 }

一旦你的检查器服务被标记,接下来你需要配置你的防火墙以使用 security.user_checker.chain.<firewall> 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
# config/packages/security.yaml

# ...
security:
    firewalls:
        api:
            pattern: ^/api
            user_checker: security.user_checker.chain.api
            # ...
        main:
            pattern: ^/
            user_checker: security.user_checker.chain.main
            # ...
本文档,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本