Symfony: проверять подлинность пользователей на сервере LDAP, но разрешать вход только в том случае, если их имя пользователя находится в пользовательской таблице БД

Сначала краткое объяснение моей задачи. Я использую Symfony 2.8 и у меня есть приложение с REST API и SonataAdminBundle. Посетители веб-сайта могут публиковать определенные данные через API REST, которые сохраняются в базе данных. Определенная группа сотрудников должна управлять этими данными через область администратора.

Доступ к административной области должен быть защищен с помощью имени пользователя и пароля. Есть сущность Employee с собственностью username, но без пароля. Аутентификация должна выполняться на сервере LDAP, но доступ к административной области должен быть ограничен только теми сотрудниками, которые присутствуют в объекте. Employee т.е. ссылающаяся таблица базы данных.

Для аутентификации LDAP я использую новый компонент LDAP в Symfony 2.8.

Помимо этого должна быть учетная запись администратора как 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 Пользователь admin может войти без проблем в любом случае.

Я ценю любую помощь. Если кто-то думает, что весь мой подход плох и знает лучший способ, пожалуйста, не жалейте критиков.

Я знаю, что есть FOSUserBundle и SonataUserBundle, но я бы предпочел пользовательского провайдера, поскольку я не хочу раздувать сущность Employee, поскольку мне действительно не нужны все эти свойства, такие как пароль, соль, isLocked и т. Д. Не думаю, что настройка SonataUserBundle в моем конкретном случае будет намного проще. Если вы все еще думаете, что есть более элегантный способ выполнить мою задачу с этими двумя связками, я буду благодарен за хороший совет.

1 ответ

Решение

Вы можете настроить user-checker для брандмауэра, ваш db-провайдер на самом деле не является user-провайдером, потому что у него нет всей информации, необходимой для аутентификации пользователя (например, пароля), так что я бы сделал, я бы удалил провайдер пользователя БД и вместо него добавьте пользовательскую проверку, основная идея пользовательской проверки заключается в добавлении дополнительных проверок в процессе аутентификации, в вашем случае нам нужно проверить, находится ли пользователь в таблице сотрудников или нет

вам нужно сделать три вещи, чтобы реализовать это

реализовать 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
            #...

Прочитайте больше

Другие вопросы по тегам