Symfony 3:如何在 Authenticator checkCredentials 中检查 LDAP

Symfony 3: How to check LDAP inside Authenticator checkCredentials

我需要使用自定义逻辑根据 LDAP 服务器对用户进行身份验证。

我正在 Symfony 3.3 中实现自定义身份验证系统,并构建了一个名为 LoginFormAuthenticator 的自定义身份验证器,它扩展了 AbstractFormLoginAuthenticator,根据: http://symfony.com/doc/current/security/guard_authentication.html

我需要根据数据库中的用户实体检查用户名,然后根据用户类型,根据存储在数据库中的 bcrypt 密码或外部 LDAP 服务器进行身份验证。

checkCredentials 方法中,我可以通过以下方式成功验证存储在数据库中的密码:

    class LoginFormAuthenticator extends AbstractFormLoginAuthenticator {
     ...

            public function checkCredentials($credentials, UserInterface $user)
            {
               ...

               // check password if the user is database user
               if ($user->getApp() == 'DB') {
                 if ($this->passwordEncoder->isPasswordValid($user, $password)) {
                      return true;
                 }
               }

               // check LDAP server if LDAP user
               if ($this->getApp() == 'LDAP') {
                if ($this->unknownLdapService->check($user, $password)
                {
                      return true;
                }
               ...

我不清楚使用本机 symfony 功能检查 LDAP 服务器的用户名和密码的正确方法。

如果我将我的配置更改为使用 form_login_ldap(而不是我的新身份验证器),它实际上会成功地针对 LDAP 进行身份验证,尽管它在哪里进行调用对我来说是模糊的。

我应该使用什么服务或 class 来查询 LDAP 来代替上面的 unknownLdapService

您可以使用自己的 LDAP 服务:您只需调用 ldap_bind。 (它可以让你做更多的 ldap 检查或模拟它)

您也可以使用 Symfony 提供程序:vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php

namespace Symfony\Component\Security\Core\Authentication\Provider;

class LdapBindAuthenticationProvider extends UserAuthenticationProvider
{
    private $userProvider;
    private $ldap;
    private $dnString;

    public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, LdapClientInterface $ldap, $dnString = '{username}', $hideUserNotFoundExceptions = true)
    {
        parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);

        $this->userProvider = $userProvider;
        $this->ldap = $ldap;
        $this->dnString = $dnString;
    }

    /**
     * {@inheritdoc}
     */
    protected function retrieveUser($username, UsernamePasswordToken $token)
    {
        if ('NONE_PROVIDED' === $username) {
            throw new UsernameNotFoundException('Username can not be null');
        }

        return $this->userProvider->loadUserByUsername($username);
    }

    /**
     * {@inheritdoc}
     */
    protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
    {
        $username = $token->getUsername();
        $password = $token->getCredentials();

        if ('' === $password) {
            throw new BadCredentialsException('The presented password must not be empty.');
        }

        try {
            $username = $this->ldap->escape($username, '', LDAP_ESCAPE_DN);
            $dn = str_replace('{username}', $username, $this->dnString);

            $this->ldap->bind($dn, $password);
        } catch (ConnectionException $e) {
            throw new BadCredentialsException('The presented password is invalid.');
        }
    }
}

我在这里最终使用的解决方案是,我首先将现有的 Symfony ldap 服务注入到我的方法的构造函数中。 ldap 服务在 services.yml 中的配置方式与 Symfony 文档为 form_login_ldap 提供商配置的方式相同。

/**
 * LoginFormAuthenticator constructor.
 * @param FormFactoryInterface $formFactory
 * @param EntityManager $em
 * @param RouterInterface $router
 * @param SecureUserPasswordEncoder $passwordEncoder
 * @param Ldap $ldap
 */
public function __construct(..., Ldap $ldap, ... )
{
    ...
    $this->ldap = $ldap;
}

然后在我的 checkCredentials 方法中,我调用了 ldap 绑定方法:

 public function checkCredentials($credentials, $userInterface $user)
    ...
    $password = $credentials['_password']; 
    $login_format = 'DOMAIN\%s';  // this is the expected format in my case
    $login_username = sprintf($login_format, $user);
    ...
    try {
        // try to bind with the username and provided password
        $this->ldap->bind($login_username, $password);
    } catch (\Symfony\Component\Ldap\Exception\ConnectionException $e) {
        //return false;
        throw new CustomUserMessageAuthenticationException('The submitted LDAP password is invalid.');
    };
        return true;
    };

这有效,如果 ldap 身份验证失败,它会抛出相应的异常。