简体   繁体   English

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

[英]Symfony: Authenticate users against LDAP server, but allow login only if their username is in custom db table

First a short explanation of my task. 首先简要解释一下我的任务。 I'm using Symfony 2.8 and have an application with REST API and SonataAdminBundle. 我正在使用Symfony 2.8,并拥有一个REST API和SonataAdminBundle应用程序。 Visitors of the website can post certain data over the REST API that is persisted to the database. 该网站的访问者可以通过持久存储到数据库的REST API发布某些数据。 A certain group of employees should manage those data through the admin area. 某组员工应通过管理区域管理这些数据。

The access to the admin area should be protected with username and password. 应使用用户名和密码保护对管理区域的访问。 There is entity Employee with a property username , but no password. 有实体Employee具有属性username ,但没有密码。 The authentication should be done against the LDAP server, but the access to admin area should be restricted only to those employees that are present in the entity Employee ie the referring database table. 应该对LDAP服务器进行身份验证,但是对管理区域的访问应该仅限于实体Employee中存在的那些员工,即引用数据库表。

For the LDAP authentication I am using the new LDAP component in Symfony 2.8. 对于LDAP身份验证,我在Symfony 2.8中使用新的LDAP组件。

Beyond that there should be an admin account as in_memory user. 除此之外,应该有一个管理员帐户作为in_memory用户。

This is what I have now: 这就是我现在拥有的:

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';
    }
}

The authentication against LDAP works like a charm. 针对LDAP的身份验证就像一个魅力。 When an employee from the database is trying to login, he/she is redirected to the homepage('/') and the login is failed. 当数据库中的员工尝试登录时,他/她将被重定向到主页('/')并且登录失败。 All other users, who are not in the database, can login without a problem. 不在数据库中的所有其他用户都可以毫无问题地登录。

That is exactly the opposite of what I want! 这正是我想要的反面!

If I chain the providers like this: 如果我像这样链接提供者:

chain_provider:
    chain:
        providers: [app_users, db_user]

then the method loadUserByUsername is not even called and all users can login, those who are in the database and those who are not. 然后甚至没有调用方法loadUserByUsername ,所有用户都可以登录,数据库中的用户和非数据库的用户。

The in_memory user admin can login without a problem in any case. 在任何情况下, in_memory用户admin都可以in_memory登录。

I appreciate any help. 我感谢任何帮助。 If someone thinks that my entire approach is bad and knows a better way, please don't spare with critics. 如果有人认为我的整个方法都很糟糕并且知道更好的方法,那么请不要饶恕批评者。

I know there is FOSUserBundle and SonataUserBundle, but I would prefer a custom user provider as I don't want to bloat the entity Employee, since I really don't need all those properties like password, salt, isLocked, etc. Also I don't think configuring SonataUserBundle in my particular case would be much simpler. 我知道有FOSUserBundle和SonataUserBundle,但我更喜欢自定义用户提供商,因为我不想膨胀实体Employee,因为我真的不需要所有这些属性,如密码,盐,isLocked等。我也不喜欢我认为在我的特定情况下配置SonataUserBundle会简单得多。 Should you still think there is a more elegant way to fulfill my task with these two bundles, I'll be grateful to get a good advice. 如果您仍然认为有更优雅的方式来完成这两个捆绑包的任务,我将很高兴得到一个好的建议。

you can configure user-checker per firewall, your db user provider isn't really a user-provider because it doesn't have all the information that's needed to authenticate a user (eg password) so what i would do is , i would remove the db user provider and add a user checker instead, the main idea of the user checker is to add additional checks during the authentication process , in your case we need to check if the user is in the employee table or not 您可以为每个防火墙配置用户检查程序,您的数据库用户提供程序实际上不是用户提供程序,因为它没有验证用户所需的所有信息(例如密码)所以我要做的是,我会删除数据库用户提供程序并添加用户检查程序,用户检查程序的主要思想是在身份验证过程中添加其他检查,在您的情况下我们需要检查用户是否在employee表中

you need to do three things to implement this 你需要做三件事来实现这个

implement UserCheckerInterface Symfony\\Component\\Security\\Core\\User\\UserCheckerInterface 实现UserCheckerInterface Symfony\\Component\\Security\\Core\\User\\UserCheckerInterface

check if the user is in the employee table or not in checkPostAuth() method checkPostAuth()方法中检查用户是否在employee表中

expose your new user-checker as a service 将您的新用户检查器作为服务公开

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

change your firewall config to use the new user-checker 更改防火墙配置以使用新的用户检查程序

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

Read more 阅读更多

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM