Symfony 5:使用自定义用户实体进行 LDAP 身份验证

2023-12-24

我想在 symfony 5 中实现以下身份验证场景:

  • User sends a login form with username and password, authentication is processed against an LDAP server
    • if authentication against the LDAP server is successful :
      • 如果有一个我的实例App\Entity\User作为与 ldap 匹配条目相同的用户名,从 ldap 服务器刷新其一些属性并返回该实体
      • 如果没有实例,则创建一个新实例App\Entity\User并返回它

我已经实现了一个防护身份验证器,它可以很好地针对 LDAP 服务器进行身份验证,但它返回给我一个实例Symfony\Component\Ldap\Security\LdapUser我不知道如何使用这个对象与其他实体建立关系!

例如,假设我有一个Car实体具有owner必须是对用户的引用的属性。

我该如何处理?

这是我的代码security.yaml file:

security:
    encoders:
        App\Entity\User:
            algorithm: auto

    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
        my_ldap:
            ldap:
                service: Symfony\Component\Ldap\Ldap
                base_dn: "%env(LDAP_BASE_DN)%"
                search_dn: "%env(LDAP_SEARCH_DN)%"
                search_password: "%env(LDAP_SEARCH_PASSWORD)%"
                default_roles: ROLE_USER
                uid_key: uid
                extra_fields: ['mail']
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: true
            lazy: true
            provider: my_ldap
            guard:
                authenticators:
                    - App\Security\LdapFormAuthenticator

我终于找到了一个很好的工作解决方案。 缺失的部分是一个自定义用户提供者 https://symfony.com/doc/current/security/user_provider.html#creating-a-custom-user-provider。 该用户提供者有责任根据 LDAP 对用户进行身份验证并返回匹配的信息App\Entity\User实体。这是在getUserEntityCheckedFromLdap的方法LdapUserProvider class.

如果没有实例App\Entity\User保存在数据库中后,自定义用户提供程序将实例化并保留它。这是first user connection用例。

完整代码可在此公共 github 存储库中找到 https://github.com/benIT/symfony5-ldap.

您将在下面找到我为使 LDAP 连接正常工作而遵循的详细步骤。

因此,让我们在中声明自定义用户提供程序security.yaml.

security.yaml:

    providers:
        ldap_user_provider:
            id: App\Security\LdapUserProvider

现在,将其配置为服务,以传递一些 ldap 有用的字符串参数services.yaml。 请注意,因为我们要自动装配Symfony\Component\Ldap\Ldap服务,我们也添加这个服务配置:services.yaml:

#see https://symfony.com/doc/current/security/ldap.html
  Symfony\Component\Ldap\Ldap:
    arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
  Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
    arguments:
      -   host: ldap
          port: 389
#          encryption: tls
          options:
            protocol_version: 3
            referrals: false

  App\Security\LdapUserProvider:
    arguments:
      $ldapBaseDn: '%env(LDAP_BASE_DN)%'
      $ldapSearchDn: '%env(LDAP_SEARCH_DN)%'
      $ldapSearchPassword: '%env(LDAP_SEARCH_PASSWORD)%'
      $ldapSearchDnString:  '%env(LDAP_SEARCH_DN_STRING)%'

注意以下的论据App\Security\LdapUserProvider来自环境变量。

.env:

LDAP_URL=ldap://ldap:389
LDAP_BASE_DN=dc=mycorp,dc=com
LDAP_SEARCH_DN=cn=admin,dc=mycorp,dc=com
LDAP_SEARCH_PASSWORD=s3cr3tpassw0rd
LDAP_SEARCH_DN_STRING='uid=%s,ou=People,dc=mycorp,dc=com'

实现自定义用户提供程序:App\Security\LdapUserProvider:

<?php

    namespace App\Security;

    use App\Entity\User;
    use Doctrine\ORM\EntityManager;
    use Doctrine\ORM\EntityManagerInterface;
    use Symfony\Component\Ldap\Ldap;
    use Symfony\Component\Ldap\LdapInterface;
    use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
    use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\User\UserProviderInterface;

    class LdapUserProvider implements UserProviderInterface
    {
        /**
         * @var Ldap
         */
        private $ldap;
        /**
         * @var EntityManager
         */
        private $entityManager;
        /**
         * @var string
         */
        private $ldapSearchDn;
        /**
         * @var string
         */
        private $ldapSearchPassword;
        /**
         * @var string
         */
        private $ldapBaseDn;
        /**
         * @var string
         */
        private $ldapSearchDnString;


        public function __construct(EntityManagerInterface $entityManager, Ldap $ldap, string $ldapSearchDn, string $ldapSearchPassword, string $ldapBaseDn, string $ldapSearchDnString)
        {
        $this->ldap = $ldap;
        $this->entityManager = $entityManager;
        $this->ldapSearchDn = $ldapSearchDn;
        $this->ldapSearchPassword = $ldapSearchPassword;
        $this->ldapBaseDn = $ldapBaseDn;
        $this->ldapSearchDnString = $ldapSearchDnString;
        }

        /**
         * @param string $username
         * @return UserInterface|void
         * @see getUserEntityCheckedFromLdap(string $username, string $password)
         */
        public function loadUserByUsername($username)
        {
        // must be present because UserProviders must implement UserProviderInterface
        }

        /**
         * search user against ldap and returns the matching App\Entity\User. The $user entity will be created if not exists.
         * @param string $username
         * @param string $password
         * @return User|object|null
         */
        public function getUserEntityCheckedFromLdap(string $username, string $password)
        {
        $this->ldap->bind(sprintf($this->ldapSearchDnString, $username), $password);
        $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_FILTER);
        $search = $this->ldap->query($this->ldapBaseDn, 'uid=' . $username);
        $entries = $search->execute();
        $count = count($entries);
        if (!$count) {
            throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
        }
        if ($count > 1) {
            throw new UsernameNotFoundException('More than one user found');
        }
        $ldapEntry = $entries[0];
        $userRepository = $this->entityManager->getRepository('App\Entity\User');
        if (!$user = $userRepository->findOneBy(['userName' => $username])) {
            $user = new User();
            $user->setUserName($username);
            $user->setEmail($ldapEntry->getAttribute('mail')[0]);
            $this->entityManager->persist($user);
            $this->entityManager->flush();
        }
        return $user;
        }

        /**
         * 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.
         *
         * @return UserInterface
         */
        public function refreshUser(UserInterface $user)
        {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
        }
        return $user;

        // Return a User object after making sure its data is "fresh".
        // Or throw a UsernameNotFoundException 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($class)
        {
        return User::class === $class || is_subclass_of($class, User::class);
        }
    }

配置防火墙以使用我们的自定义用户提供程序:

security.yaml

firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    main:
        anonymous: true
        lazy: true
        provider: ldap_user_provider
        logout:
            path:   app_logout
        guard:
            authenticators:
                - App\Security\LdapFormAuthenticator

编写一个身份验证守卫:

App\SecurityLdapFormAuthenticator:

<?php

namespace App\Security;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class LdapFormAuthenticator extends AbstractFormLoginAuthenticator
{
    use TargetPathTrait;

    private $urlGenerator;

    private $csrfTokenManager;

    public function __construct(UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager)
    {
        $this->urlGenerator = $urlGenerator;
        $this->csrfTokenManager = $csrfTokenManager;
    }


    public function supports(Request $request)
    {
        return 'app_login' === $request->attributes->get('_route') && $request->isMethod('POST');
    }


    public function getCredentials(Request $request)
    {
        $credentials = [
            'username' => $request->request->get('_username'),
            'password' => $request->request->get('_password'),
            'csrf_token' => $request->request->get('_csrf_token'),
        ];
        $request->getSession()->set(
            Security::LAST_USERNAME,
            $credentials['username']
        );
        return $credentials;
    }


    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
        if (!$this->csrfTokenManager->isTokenValid($token)) {
            throw new InvalidCsrfTokenException();
        }
        $user = $userProvider->getUserEntityCheckedFromLdap($credentials['username'], $credentials['password']);
        if (!$user) {
            throw new CustomUserMessageAuthenticationException('Username could not be found.');
        }
        return $user;
    }


    public function checkCredentials($credentials, UserInterface $user)
    {
        //in this scenario, this method is by-passed since user authentication need to be managed before in getUser method.
        return true;
    }


    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        $request->getSession()->getFlashBag()->add('info', 'connected!');
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }
        return new RedirectResponse($this->urlGenerator->generate('app_homepage'));
    }

    protected function getLoginUrl()
    {
        return $this->urlGenerator->generate('app_login');
    }
}

我的用户实体如下所示:

`App\Entity\User`: 

    <?php

    namespace App\Entity;

    use App\Repository\UserRepository;
    use Doctrine\ORM\Mapping as ORM;
    use Symfony\Component\Security\Core\User\UserInterface;

    /**
     * @ORM\Entity(repositoryClass=UserRepository::class)
     */
    class User implements UserInterface
    {
        /**
         * @ORM\Id()
         * @ORM\GeneratedValue()
         * @ORM\Column(type="integer")
         */
        private $id;

        /**
         * @ORM\Column(type="string", length=180, unique=true)
         */
        private $email;

        /**
         * @var string The hashed password
         * @ORM\Column(type="string")
         */
        private $password = 'password is not managed in entity but in ldap';

        /**
         * @ORM\Column(type="string", length=255)
         */
        private $userName;

        /**
         * @ORM\Column(type="json")
         */
        private $roles = [];


        public function getId(): ?int
        {
        return $this->id;
        }

        public function getEmail(): ?string
        {
        return $this->email;
        }

        public function setEmail(string $email): self
        {
        $this->email = $email;

        return $this;
        }

        /**
         * A visual identifier that represents this user.
         *
         * @see UserInterface
         */
        public function getUsername(): string
        {
        return (string) $this->email;
        }

        /**
         * @see UserInterface
         */
        public function getRoles(): array
        {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
        }

        public function setRoles(array $roles): self
        {
        $this->roles = $roles;

        return $this;
        }

        /**
         * @see UserInterface
         */
        public function getPassword(): string
        {
        return (string) $this->password;
        }

        public function setPassword(string $password): self
        {
        $this->password = $password;

        return $this;
        }

        /**
         * @see UserInterface
         */
        public function getSalt()
        {
        // not needed when using the "bcrypt" algorithm in security.yaml
        }

        /**
         * @see UserInterface
         */
        public function eraseCredentials()
        {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
        }

        public function setUserName(string $userName): self
        {
        $this->userName = $userName;

        return $this;
        }
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Symfony 5:使用自定义用户实体进行 LDAP 身份验证 的相关文章

  • Doctrine QueryBuilder 重用部件

    我想计算所有符合我的条件的字段 并使用学说查询生成器逐页获取它们 我生成的查询取决于我的过滤器字段 第一部分是计算记录 以便我可以计算页数 qb em gt createQueryBuilder qb gt select COUNT m i
  • Symfony2 - 多种形式的主题

    有没有办法在同一页面上的两个 或多个 表单使用不同的主题 我有 2 个表单 我想对第一个表单使用主题 X 对第二个表单使用主题 Y 您需要在显示表单之前声明您的主题 你应该试试 form theme form ThemeX html twi
  • 手动渲染 Twig 字符串时禁用 HTML 转义

    我有以下代码将字符串呈现为 HTML 输出 如何阻止它转义 HTML 文本 template who bar params array who gt Foo s twig new Twig Environment new Twig Load
  • Symfony2 - Doctrine - 更新后没有更改集

    因此 当实体的某个值发生更改时 我会发送电子邮件 我只想在更新后发送电子邮件 以防更新因任何原因失败 所以在更新前我可以这样做 public function preUpdate LifecycleEventArgs args if arg
  • 如何使用 Elastica Search 和 Symfony2 执行嵌套查询

    我有一个食谱实体 其中有一些标签 多对多映射 我想按标签搜索食谱 这是我的食谱实体 ORM Entity ORM Table name recipes ORM HasLifecycleCallbacks ExclusionPolicy al
  • Doctrine 自动递增起始值@ORM\GenelatedValue

    如何使用注释设置自动递增 id 的起始值 我希望它从 250000 开始 ORM Id ORM GeneratedValue ORM Column type integer protected id ORM Id ORM Generated
  • XAMPP 7.0.4 PHP 7 LDAPS 连接无法绑定 LDAP 工作正常

    我通过 LDAP 与域控制器的连接很好 但是当我尝试通过 LDAPS 连接时 它无法绑定 我已经添加了 c OpenLDAP sysconf ldap conf 路径和文件 并尝试按照几个站点的建议将 TLS REQCERT never 添
  • 如何配置 FOSRestBundle 以不干扰自定义异常控制器

    我刚刚将我的 Symfony 2 7 页面更新到 2 8 除了 Symfony 本身之外 许多其他软件包也已更新 FOSRestBundle已从1 4版本更新至2 1版本 更新后CustomExceptionController我配置为Tw
  • FOSUserBundle 和 ACL 业务角色

    这个周末我开始学习 Symfony 2 我没有遇到任何问题 因为我认为该框架有详细记录 我正在使用 FOSUserBundle 包进行 ACL 我想知道是否可以使其类似于 Yii 框架 bizRule return Yii app gt u
  • Symfony 2 FOSUserBundle 与产品表的关系

    如果之前有人问过这个问题 我提前道歉 我已成功设置 FOSUserBundle 我正在尝试设置 http symfony com doc current book doctrine html http symfony com doc cur
  • 在 Symfony2 中使用 assetic 加载一个 js 文件

    我是 Symfony 2 的新手 之前我经常使用 Codeigniter 现在我正在探索 assetic 我无法理解如何将单个文件添加到已经加载的 JS 文件堆栈中 我有一个主页树枝文件 其中包含以下内容 javascripts BlogB
  • 使用 Novell LDAP 对 .NET Core 中的 AD 进行页面 LDAP 查询

    我正在使用 Novell LDAP 库从 NET 代码应用程序查询 Active Directory 大多数查询都会成功 但有些查询会返回 1000 多个结果 而 AD 服务器会拒绝这些结果 因此 我试图找出如何使用 Novell 的库来分
  • 使用自定义服务的编译器传递加载 Symfony 的参数

    根据这个问题如何从数据库加载 Symfony 的配置参数 Doctrine https stackoverflow com q 28713495 8945214我有一个类似的问题 我需要动态设置参数 并且我想提供来自另一个自定义服务的数据
  • 如何使用 Twig 的属性函数访问嵌套对象属性

    我试图使用一个树枝变量来访问另一个树枝变量的属性 直到我找到 属性 函数为止 该变量才起作用 除了需要访问嵌套属性的情况外 效果很好 当包含属性的变量实际上是对象 属性时 它不起作用 例如 attribute object1 variabl
  • Sonata DateTimePickerType 类默认日期显示错误的日期时间格式

    我陷入困境 我不知道如何使用 sonata DateTimePickerType 类正确设置默认日期和时间 我尝试了不同的方法 但到目前为止 没有一种方法没有帮助 在下面的截图中 help 键显示正确的日期和时间 但是当我使用 dp 默认日
  • 如何使用 JNDI 和 Digest-MD5 对 LDAP 进行身份验证

    我正在尝试使用 DIGEST MD5 加密对 LDAP 服务器进行身份验证 使用简单加密时 它工作得很好 但由于显而易见的原因 我无法通过网络以纯文本形式发送密码 奇怪的是 在使用 Softerra LDAP 浏览器时 我可以使用 Dige
  • 在 Symfony3 中覆盖 Doctrine2 类型

    我想用Carbon http carbon nesbot com docs 我的 Symfony 3 2 应用程序中的对象而不是 SPL DateTime 对象 我发现了一组 DoctrineExtension 类here https gi
  • 检查用户是否存在于 Active Directory 中

    我正在使用 vb net 我想检查 Active Directory 中是否存在特定用户 如果是 我想显示特定用户的详细信息 怎么做 用户登录凭据通过文本框控件传递 My code Dim de As DirectoryEntry GetD
  • 1.2.840.113556.1.4.1941 (LDAP_MATCHING_RULE_IN_CHAIN) 存在性能问题?

    LDAP 搜索有一些内置规则 其中之一是LDAP MATCHING RULE IN CHAIN From MSDN https msdn microsoft com en us library aa746475 v vs 85 aspx 1
  • Doctrine EntityManager 清除嵌套实体中的方法

    我想用学说批量插入处理 http doctrine orm readthedocs org en latest reference batch processing html为了优化大量实体的插入 问题出在 Clear 方法上 它表示此方法

随机推荐