代码之家  ›  专栏  ›  技术社区  ›  Element Zero

在symfony4中将身份验证与用户实体分离?

  •  0
  • Element Zero  · 技术社区  · 7 年前

    在使用同一个实体维护员工和跟踪登录我的网站时遇到了一些问题,我发现了一个帖子 here 这涉及到将安全用户解耦。不幸的是,我觉得这篇文章在如何完全实现它方面有点欠缺(而且似乎我不是唯一一个)。首先,为什么我需要这样做,我将如何在symfony4中实现它?

    1 回复  |  直到 7 年前
        1
  •  2
  •   Element Zero    7 年前

    我为什么要这么做?

    here

    …这也会带来副作用: 您将在开发人员倾向于使用的会话中使用此实体 在会话中,您将遇到同步问题。如果你 不是数据库里的。为了解决这个问题,你可以 每个请求都将实体合并回实体管理器。

    虽然这解决了其中一个问题,但另一个常见问题是 其他物体,这会带来一些副作用:

    (标准设置),它将尝试序列化包含 一种联系。这将喷出一些错误在您的屏幕上作为 无法序列化连接。哦,别想了 不完整对象的非序列化问题,因为缺少 属性。每个经过身份验证的用户都会触发此情况。

    基本上,问题是如果您使用同一个实体进行身份验证和处理用户/员工/客户机等,您将遇到这样的问题:当您更改实体的属性时,它将导致已验证的用户与数据库中的内容不同步-导致角色不正确的问题,用户突然被迫注销(由于 logout_on_user_change setting ),或其他问题,具体取决于用户类在系统中的使用方式。

    我假设您有一个“User”实体,它至少有用户名、密码和角色

    为了解决这个问题,我们需要创建两个独立的服务,作为用户实体和身份验证用户之间的桥梁。

    第一个是创建一个安全用户,它使用user类中的字段

    /app/Security/SecurityUser.php

    <?php
    
    namespace App\Security;
    
    use App\Entity\User;
    use Symfony\Component\Security\Core\User\UserInterface;
    
    class SecurityUser implements UserInterface, \Serializable
    {
        private $username;
        private $password;
        private $roles;
    
        public function __construct(User $user)
        {
            $this->username = $user->getUsername();
            $this->password = $user->getPassword();
            $this->roles = $user->getRoles();
        }
    
        public function getUsername(): ?string
        {
            return $this->username;
        }
    
        public function getPassword(): ?string
        {
            return $this->password;
        }
    
        public function getSalt()
        {
            // you *may* need a real salt depending on your encoder
            // see section on salt below
            return null;
        }
    
        /** @see \Serializable::serialize() */
        public function serialize()
        {
            return serialize(array(
                $this->username,
                $this->password,
                // Should only be set if your encoder uses a salt i.e. PBKDF2
                // This example uses Argon2i
                // $this->salt,
            ));
        }
    
        /** @see \Serializable::unserialize() */
        public function unserialize($serialized)
        {
            list (
                $this->username,
                $this->password,
                // Should only be set if your encoder uses a salt i.e. PBKDF2
                // This example uses Argon2i
                // $this->salt
                ) = unserialize($serialized, array('allowed_classes' => false));
        }
    
        public function getRoles()
        {
            return $this->roles;
        }
    
        public function eraseCredentials()
        {
        }
    }
    

    安全用户提供程序 /应用程序/安全/安全用户提供程序

    <?php
    
    namespace App\Security;
    
    use App\Repository\UserRepository;
    use Symfony\Component\Security\Core\User\UserProviderInterface;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
    use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
    
    class SecurityUserProvider implements UserProviderInterface
    {
        private $userRepository;
    
        public function __construct(UserRepository $userRepository)
        {
            $this->userRepository = $userRepository;
        }
    
        public function loadUserByUsername($username)
        {
            return $this->fetchUser($username);
        }
    
        public function refreshUser(UserInterface $user)
        {
            if (!$user instanceof SecurityUser) {
                throw new UnsupportedUserException(
                    sprintf('Instances of "%s" are not supported.', get_class($user))
                );
            }
    
            $username = $user->getUsername();
    
            $this->logger->info('Username (Refresh): '.$username);
    
            return $this->fetchUser($username);
        }
    
        public function supportsClass($class)
        {
            return SecurityUser::class === $class;
        }
    
        private function fetchUser($username)
        {
            if (null === ($user = $this->userRepository->findOneBy(['username' => $username]))) {
                throw new UsernameNotFoundException(
                    sprintf('Username "%s" does not exist.', $username)
                );
            }
    
            return new SecurityUser($user);
        }
    }
    

    这个服务基本上会要求查询数据库中的用户名,然后查询相关用户名的角色。如果找不到用户名,则会创建一个错误。然后将SecurityUser对象返回给Symfony进行身份验证。

    现在我们需要告诉Symfony使用这个对象

    /app/config/packages/security.yaml文件

    security:
        ...
        providers:
            db_provider:
                id: App\Security\SecurityUserProvider
    

    “db\u provider”这个名字并不重要-你可以使用任何你想要的东西。此名称仅用于将提供程序映射到防火墙。如何配置防火墙超出了本文档的范围,请参阅 here

    security:
        ...
        firewalls:
            dev:
                pattern: ^/(_(profiler|wdt)|css|images|js)/
                security: false
            main:
                pattern:    ^/
                anonymous: ~
                provider: db_provider
                form_login:
                    login_path: login
                    check_path: login
                logout:
                    path:   /logout
                    target: /
                    invalidate_session: true
    
        access_control:
            - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
            - { path: ^/, roles: ROLE_USER }
    

    最后,我们需要配置一个编码器,以便我们可以加密密码。

    security:
        ...
        encoders:
            App\Security\SecurityUser:
                algorithm: argon2i
                memory_cost: 102400
                time_cost: 3
                threads: 4
    

    注意,我使用的是Argon2i。内存开销、时间开销和线程的值是非常主观的,具体取决于您的系统。你可以看到我的帖子 here 它可以帮助您获得系统的正确值

    在这一点上,你的安全性应该是工作的,你已经完全脱离了你的用户实体-恭喜!

    here .

    推荐文章