Symfony:针对LDAP服务器对用户进行身份验证,但仅当用户名在自定义db表中时才允许登录

时间:2016-03-15 16:58:02

标签: php symfony authentication ldap

首先简要解释一下我的任务。我正在使用Symfony 2.8,并拥有一个REST API和SonataAdminBundle应用程序。该网站的访问者可以通过持久存储到数据库的REST API发布某些数据。某组员工应通过管理区域管理这些数据。

应使用用户名和密码保护对管理区域的访问。实体Employee具有属性username,但没有密码。应该对LDAP服务器进行身份验证,但是对管理区域的访问应仅限于实体Employee中存在的那些员工,即引用数据库表。

对于LDAP身份验证,我在Symfony 2.8中使用新的LDAP组件。

除此之外,应该有一个管理员帐户in_memory用户。

这就是我现在所拥有的:

app/config/services.yml

services:
    app.ldap:
        class: Symfony\Component\Ldap\LdapClient
        arguments: ["ldaps://ldap.uni-rostock.de"]

    app.db_user_provider:
        class: AppBundle\Security\DbUserProvider
        arguments: ["@doctrine.orm.entity_manager"]

app/config/security.yml

security:
    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    providers:
        chain_provider:
            chain:
                providers: [db_user, app_users]

        in_memory:
            memory:
                users:
                    admin: { password: adminpass, roles: 'ROLE_ADMIN' }

        app_users:
            ldap:
                service: app.ldap
                base_dn: ou=people,o=uni-rostock,c=de
                search_dn: uid=testuser,ou=people,o=uni-rostock,c=de
                search_password: testpass
                filter: (uid={username})
                default_roles: ROLE_USER

        db_user:
            id: app.db_user_provider

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        admin:
            anonymous: true
            pattern:   ^/
            form_login_ldap:
                provider: chain_provider
                service: app.ldap
                dn_string: "uid={username},ou=people,o=uni-rostock,c=de"
                check_path: /login_check
                login_path: /login
            form_login:
                provider: in_memory
                check_path: /login_check
                login_path: /login
            logout:
                path: /logout
                target: /

    access_control:
        - { path: ^/admin, roles: ROLE_USER }

    encoders:
        Symfony\Component\Security\Core\User\User: plaintext
        AppBundle\Entity\Employee: bcrypt

src/AppBundle/Entity/Employee.php     

namespace AppBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Doctrine\ORM\Mapping as ORM;

class Employee implements UserInterface, EquatableInterface
{
    // other properties

    private $username;

    // getters and setters for the other properties

    public function getUsername()
    {
        return $this->username;
    }

    public function getRoles()
    {
        return array('ROLE_USER');
    }

    public function getPassword()
    {
        return null;
    }

    public function getSalt()
    {
        return null;
    }

    public function eraseCredentials()
    {
    }

    public function isEqualTo(UserInterface $user)
    {
        if (!$user instanceof Employee) {
            return false;
        }

        if ($this->username !== $user->getUsername()) {
            return false;
        }

        return true;
    }
}

src/AppBundle/Security/DbUserProvider.php

<?php

namespace AppBundle\Security;

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;
use Doctrine\ORM\EntityManager;
use AppBundle\Entity\Employee;

class DbUserProvider implements UserProviderInterface
{
    private $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function loadUserByUsername($username)
    {
        $repository = $this->em->getRepository('AppBundle:Employee');
        $user = $repository->findOneByUsername($username);

        if ($user) {
            return new Employee();
        }

        throw new UsernameNotFoundException(
            sprintf('Username "%s" does not exist.', $username)
        );
    }

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

        return $this->loadUserByUsername($user->getUsername());
    }

    public function supportsClass($class)
    {
        return $class === 'AppBundle\Entity\Employee';
    }
}

针对LDAP的身份验证就像魅力一样。当数据库中的员工尝试登录时,他/她将被重定向到主页('/')并且登录失败。不在数据库中的所有其他用户都可以毫无问题地登录。

这与我想要的完全相反!

如果我像这样链接提供者:

chain_provider:
    chain:
        providers: [app_users, db_user]

然后甚至没有调用方法loadUserByUsername,所有用户都可以登录,数据库中的用户和非数据库的用户。

in_memory用户管理员无论如何都可以毫无问题地登录。

我感谢任何帮助。如果有人认为我的整个方法都很糟糕并且知道更好的方法,那么请不要饶恕批评者。

我知道有FOSUserBundle和SonataUserBundle,但我更喜欢自定义用户提供程序,因为我不想膨胀实体Employee,因为我真的不需要所有这些属性,如password,salt,isLocked等。另外,我不认为在我的特定情况下配置SonataUserBundle会更简单。如果您仍然认为有更优雅的方式来完成我的任务,我会很高兴得到一个好的建议。

1 个答案:

答案 0 :(得分:4)

您可以为每个防火墙配置用户检查程序,您的数据库用户提供程序实际上不是用户提供程序,因为它没有验证用户身份所需的所有信息(例如密码) ) 所以我要做的是,我会删除数据库用户提供程序并添加用户检查程序,用户检查程序的主要思想是在身份验证过程中添加其他检查,在您的情况下我们需要检查用户是否在雇员表是否

你需要做三件事来实现这个

实施UserCheckerInterface Symfony\Component\Security\Core\User\UserCheckerInterface

checkPostAuth()方法

中检查用户是否在员工表中

将您的新用户检查器公开为服务

services:
    app.employee_user_checker:
        class: Path\To\Class\EmployeeUserChecker

更改防火墙配置以使用新的用户检查程序

security:
    firewalls:
        admin:
            pattern: ^/admin
            user_checker: app.employee_user_checker
            #...

Read more