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