在没有密码的情况下在数据库中保存 LDAP 用户实体

Saving LDAP User Entity in Database without Password

理论:有没有办法将 Ldap 用户存储在您自己的数据库中,对于关系来说是必需的,没有密码并且仍然可以使 refreshUser 工作?

我更改了我的系统,通过检查每次登录是否与 ldap 服务器用户密码匹配,无需存储密码即可工作。 Symfony 在每个请求上调用您的提供者的 refreshUser 方法,我有一个自定义 LDAP 提供者,使用数据库中的学说刷新用户。 Symfony 确保对象在编辑后始终是最新的。

问题是此方法调用了用户实体 getPassword() 方法,由于数据库中的 none pwd,该方法未实现。

我看到了一种修复它的方法,即在会话中保存实体,并且每次调用 refreshUser() 时,返回会话实体。但是没有办法让它在编辑后总是更新。而且我不想破坏 symfony 基础设施。

那么有没有办法 refreshUser() 再次调用 LDAP 服务器并询问信息是否仍然匹配?

-已解决-

任何拥有依赖于 ldap 服务器身份验证的应用程序和应用程序数据库中的自定义用户实体的人,但出于安全原因不想添加密码。这是一个答案:

0。删除 db

中的 pwd 属性等价物

在您的自定义用户实体中删除 @Orm 注释以不将密码保存到您的数据库中。保留属性和Getter/Setter。

例如:

//...
private string $password;
//...

1.添加自定义 LdapProvider

创建一个扩展 Symfony\Component\Ldap\Security\LdapUserProvider 的新 class。您可能需要通过在 services.yaml:

中添加自定义 Provider 来手动处理 DependencyInjections
fullyQualified\NameSpace\CustomLdapProvider:
    arguments:
      - '@Doctrine\ORM\EntityManagerInterface' # I need the EM in __construct
      - '@Symfony\Component\Ldap\Ldap'
      - # baseDn
      - # searchDN
      - # searchPassword
      - # roles
      - # uidKey
      - # filter optionaly, add null
      - # passwordAttribute

使用以下代码覆盖方法 'supportsClass' 和 'refreshUser':

    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
        }

        return $this->entityManager->getRepository(User::class)->find($user);
    }

    public function supportsClass(string $class): bool
    {
        return User::class === $class || is_subclass_of($class, User::class) || LdapUser::class === $class || is_subclass_of($class, LdapUser::class);
    }

您的 refreshUser() 方法需要 return 一个用户实体。在我的例子中,我只是 return 来自数据库的用户。

重要的是,您的 supportsClass() 允许 LdapUser 和您自己的用户实体,因为在第一次登录时,您将从 ldap 服务器获得一个用户 Symfony\Component\Ldap\Security\LdapUser

2。在 InteractiveLogin

上添加一个 EventSubscriber

现在您需要创建一个自己的用户,如果没有自己的用户,则继续使用自己的用户实体登录。

创建一个扩展 Symfony\Component\EventDispatcher\EventSubscriberInterface 的新 class 并使用事件 'onInteractiveLogIn'.

    public static function getSubscribedEvents()
    {
        return [
            SecurityEvents::INTERACTIVE_LOGIN => [
                ['onInteractiveLogIn', 0],
            ],
        ];
    }

在 'onInteractiveLogIn()' 中,您需要实现创建新的自定义用户实体的方法,如果没有找到并保留它。同时创建一个新的 Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; 用于登录。

我的例子:

 public function onInteractiveLogIn(InteractiveLoginEvent $interactiveLoginEvent)
    {
        $ldapUser = $interactiveLoginEvent->getAuthenticationToken()->getUser();
        if (!($ldapUser instanceof LdapUser)) {
            return;
        }
        $localUser = // find local User with LdapUser credentials (Query);

        if (!$localUser) {
            // no custom user found, create own
            $localUser = new User();
            //...
        }
        // since we do not have pwd, fill it with anything to generate random token
        $rmdBytes = random_bytes(32);
        $localUser->setPassword($rmdBytes);

        $this->entityManager->persist($localUser);
        $this->entityManager->flush();

        // login user with new created/already found custom user entity
        $token = new UsernamePasswordToken($localUser, $rmdBytes, 'main', $localUser->getRoles());
        $this->tokenStorage->setToken($token);
    }

重要提示:onInteractiveLogIn已经验证输入的密码等于真实的ldap密码。

3。实现自己的方式来刷新 User

由于 Symfony 在每个新请求上调用 refreshUser() 方法来提供始终新鲜的数据对象,我们需要覆盖默认行为。

您的 User 实体需要实施 Symfony\Component\Security\Core\User\EquatableInterface

您可以在此处更改 refreshUser() 的调用方式,比较新刷新的用户和原始用户。默认是密码,但由于我们没有密码,我们不希望出现这种情况。

在此示例中,我比较了 UserIdentifieres,因为它们是唯一的:

    public function isEqualTo(UserInterface $user)
    {
        return $this->getUserIdentifier() === $user->getUserIdentifier();
    }
  1. 完成,测试一下。

您现在应该可以登录,而无需将密码保存在您自己的数据库中。 新系统将在每次登录 Ldap 服务器时调用,并询问具有给定凭据的用户。如果随后登录,它将不再调用服务器,而是在匹配 isEqualTo().

时自动刷新用户