跳到内容

用户提供器

编辑此页

用户提供器基于“用户标识符”(例如,用户的电子邮件地址或用户名)从存储(例如,数据库)重新加载用户。有关何时使用用户提供器的更多详细信息,请参阅安全

Symfony 提供了几种用户提供器

实体用户提供器
使用 Doctrine 从数据库加载用户;
LDAP 用户提供器
从 LDAP 服务器加载用户;
内存用户提供器
从配置文件加载用户;
链式用户提供器
将两个或多个用户提供器合并为一个新的用户提供器。

实体用户提供器

这是最常用的用户提供器。用户存储在数据库中,用户提供器使用 Doctrine 来检索它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# config/packages/security.yaml
security:
    providers:
        users:
            entity:
                # the class of the entity that represents users
                class: 'App\Entity\User'
                # the property to query by - e.g. email, username, etc
                property: 'email'

                # optional: if you're using multiple Doctrine entity
                # managers, this option defines which one to use
                #manager_name: 'customer'

    # ...

使用自定义查询加载用户

实体提供器只能从一个 *特定* 字段查询,该字段由 property 配置键指定。如果您想要更多地控制此行为 - 例如,您想通过 email *或* username 查找用户,您可以通过在您的 Doctrine 仓库(例如 UserRepository)中实现 UserLoaderInterface 来实现。

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
// src/Repository/UserRepository.php
namespace App\Repository;

use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;

class UserRepository extends ServiceEntityRepository implements UserLoaderInterface
{
    // ...

    public function loadUserByIdentifier(string $usernameOrEmail): ?User
    {
        $entityManager = $this->getEntityManager();

        return $entityManager->createQuery(
                'SELECT u
                FROM App\Entity\User u
                WHERE u.username = :query
                OR u.email = :query'
            )
            ->setParameter('query', $usernameOrEmail)
            ->getOneOrNullResult();
    }
}

为了完成此操作,请从 security.yaml 中的用户提供器中删除 property 键。

1
2
3
4
5
6
7
8
# config/packages/security.yaml
security:
    providers:
        users:
            entity:
                class: App\Entity\User

    # ...

现在,每当 Symfony 使用用户提供器时,都会调用您的 UserRepository 上的 loadUserByIdentifier() 方法。

内存用户提供器

不建议在实际应用中使用此提供器,因为它存在局限性且用户管理难度大。它可能在应用原型和不将用户存储在数据库中的有限应用中很有用。

此用户提供器将所有用户信息(包括密码)存储在配置文件中。确保密码已正确哈希。有关更多信息,请参阅密码哈希和验证

设置哈希后,您可以在 security.yaml 中配置所有用户信息。

1
2
3
4
5
6
7
8
9
10
# config/packages/security.yaml
security:
    providers:
        backend_users:
            memory:
                users:
                    john_admin: { password: '$2y$13$jxGxc ... IuqDju', roles: ['ROLE_ADMIN'] }
                    jane_admin: { password: '$2y$13$PFi1I ... rGwXCZ', roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN'] }

    # ...

警告

当使用 memory 提供器,而不是 auto 算法时,您必须选择不带盐的编码(即 bcrypt)。

链式用户提供器

此用户提供器组合了两个或多个其他提供器,以创建一个新的用户提供器。配置提供器的顺序很重要,因为 Symfony 将从第一个提供器开始查找用户,并继续在其他提供器中查找,直到找到用户为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# config/packages/security.yaml
security:
    # ...
    providers:
        backend_users:
            ldap:
                # ...

        legacy_users:
            entity:
                # ...

        users:
            entity:
                # ...

        all_users:
            chain:
                providers: ['legacy_users', 'users', 'backend_users']

创建自定义用户提供器

大多数应用不需要创建自定义提供器。如果您将用户存储在数据库、LDAP 服务器或配置文件中,Symfony 都支持。但是,如果您从自定义位置(例如,通过 API 或旧版数据库连接)加载用户,则需要创建自定义用户提供器。

首先,请确保您已按照安全指南创建了您的 User 类。

如果您使用 make:user 命令创建了您的 User 类(并且您回答了表明您需要自定义用户提供器的问题),则该命令将生成一个不错的骨架来帮助您入门。

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// src/Security/UserProvider.php
namespace App\Security;

use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class UserProvider implements UserProviderInterface, PasswordUpgraderInterface
{
    /**
     * Symfony calls this method if you use features like switch_user
     * or remember_me. If you're not using these features, you do not
     * need to implement this method.
     *
     * @throws UserNotFoundException if the user is not found
     */
    public function loadUserByIdentifier(string $identifier): UserInterface
    {
        // Load a User object from your data source or throw UserNotFoundException.
        // The $identifier argument is whatever value is being returned by the
        // getUserIdentifier() method in your User class.
        throw new \Exception('TODO: fill in loadUserByIdentifier() inside '.__FILE__);
    }

    /**
     * Refreshes the user after being reloaded from the session.
     *
     * When a user is logged in, at the beginning of each request, the
     * User object is loaded from the session and then this method is
     * called. Your job is to make sure the user's data is still fresh by,
     * for example, re-querying for fresh User data.
     *
     * If your firewall is "stateless: true" (for a pure API), this
     * method is not called.
     */
    public function refreshUser(UserInterface $user): UserInterface
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
        }

        // Return a User object after making sure its data is "fresh".
        // Or throw a UserNotFoundException if the user no longer exists.
        throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__);
    }

    /**
     * Tells Symfony to use this provider for this User class.
     */
    public function supportsClass(string $class): bool
    {
        return User::class === $class || is_subclass_of($class, User::class);
    }

    /**
     * Upgrades the hashed password of a user, typically for using a better hash algorithm.
     */
    public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
    {
        // TODO: when hashed passwords are in use, this method should:
        // 1. persist the new password in the user storage
        // 2. update the $user object with $user->setPassword($newHashedPassword);
    }
}

大部分工作已经完成!阅读代码中的注释并更新 TODO 部分以完成用户提供器。完成后,通过在 security.yaml 中添加用户提供器来告知 Symfony。

1
2
3
4
5
6
# config/packages/security.yaml
security:
    providers:
        # the name of your user provider can be anything
        your_custom_user_provider:
            id: App\Security\UserProvider

最后,更新 config/packages/security.yaml 文件,在所有将使用此自定义用户提供器的防火墙中,将 provider 键设置为 your_custom_user_provider

包括代码示例在内的这项工作,根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本