在 Symfony 和 ApiPlatform 项目上更改 Ldap 身份验证和 JWT 创建之间的用户信息

问题描述 投票:0回答:1

我正在尝试使用 LDAP 和 JWT 实现用户身份验证。 我正在使用 Symfony 6.4 和 ApiPlatform 3.4

流程如下:

用户在 /login_check 路由上输入用户名和密码。 凭据将根据 LDAP 服务器进行验证。 如果成功,我会从 LDAP 检索用户信息。 在生成 JWT 令牌之前,我想从应用程序的数据库中获取相应的用户实体并将其信息注入到 JWT 中。 但是,当我解码 JWT 时,与该用户关联的角色与我在数据库中存储的该用户的角色不匹配。

这是我的代码

class CustomLdapAuthenticator extends AbstractAuthenticator
{
    private $ldap;
    private $userProvider;

    public function __construct(
        private LdapCheck $ldapCheck,
        LdapInterface $ldap, 
        CustomUserProvider $userProvider

    ){
        $this->ldap = $ldap;
        $this->userProvider = $userProvider;
    }

    public function supports(Request $request): ?bool
    {
        return $request->getPathInfo() === '/login_check' && $request->isMethod('POST');
    }

    public function authenticate(Request $request): Passport
    {
        $data = json_decode($request->getContent(), true);
        $username = $data['username'] ?? '';
        $password = $data['password'] ?? '';

        if (!$username || !$password) {
            throw new AuthenticationException('blablabla');
        }

        try {
            $this->ldapCheck->getEntryFromLdap($username, $password);
        } catch (LdapException $e) {
            throw new AuthenticationException('blablabla');
        }

        $user = $this->userProvider->loadUserByIdentifier($username);

        return new SelfValidatingPassport(new UserBadge($username, function() use ($user) {
            return $user;
        }));
    }

    public function onAuthenticationSuccess(Request $request, $token, string $firewallName): ?JsonResponse
    {
        return null;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?JsonResponse
    {
        return new JsonResponse(['error' => $exception->getMessage()], 401);
    }
}
class LdapCheck
{
    public function __construct(
        private Ldap $ldap,
        private Adapter $ldapAdapter,
        // ---Those are the parameters that are bind in the services.yaml file
        private string $ldapServiceDn,
        private string $ldapServiceUser,
        private string $ldapServicePassword,
    ) {
        $this->ldap = new Ldap($this->ldapAdapter);
        try {
            $this->ldap->bind($ldapServiceUser, $ldapServicePassword);
        } catch (ConnectionException $e) {
            throw $e;
        }
    }

    /**
     * Get an entry from the Ldap.
     */
    public function getEntryFromLdap(string $username, string $password): ?Entry
    {
        $ldap = new Ldap($this->ldapAdapter);
        $search = false;
        $value = null;

        try {
            $userDn = implode(',', ['uid='.$username, $this->ldapServiceDn]);
            $ldap->bind($userDn, $password);
            if ($this->ldapAdapter->getConnection()->isBound()) {
                $search = $ldap->query(
                    'dc=domain,dc=com',
                    '(&(objectClass=person)(| (uid='.$username.')))'
                )->execute()->toArray();
            }
        } catch (ConnectionException $e) {
            return null;
        }
        if ($search && 1 === count($search)) {
            $value = $search[0];
        }

        return $value;
    }
}
class CustomUserProvider implements UserProviderInterface
{
    private $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    /**
     * @throws UserNotFoundException if the user is not found
     */
    public function loadUserByIdentifier(string $identifier): UserInterface
    {
        $user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $identifier]);

        if (!$user) {
            throw new UserNotFoundException('User not found.');
        }

        return $user;
    }

    public function refreshUser(UserInterface $user): UserInterface
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
        }

        throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__);
    }

    public function supportsClass(string $class): bool
    {
        return User::class === $class || is_subclass_of($class, User::class);
    }
}

和配置文件

security:
    # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
    providers:
        # users_in_memory: { memory: null }
        app_user_provider:
            entity:
                class: App\Entity\User
                property: userIdentifier

        my_ldap:
            ldap:
                service: Symfony\Component\Ldap\Ldap
                base_dn: '%env(LDAP_BASE_DN)%' 
                search_dn: '%env(LDAP_SEARCH_DN)%' # AD user (read only)
                search_password: '%env(LDAP_SEARCH_PASSWORD)%' # AD password (read only)
                uid_key: uid

        main:
            chain:
                providers: ['my_ldap', 'app_user_provider']

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

        api:
            pattern: ^/
            stateless: true
            custom_authenticators:
                - App\Security\CustomLdapAuthenticator
            provider: app_user_provider
            json_login_ldap:
                provider: main
                service: Symfony\Component\Ldap\Ldap
                dn_string: '%env(LDAP_DN_STRING)%'
                check_path: /login_check
                username_path: username
                password_path: password
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure
            entry_point: jwt
            jwt: ~
            refresh_jwt:
                # check_path: /api/token/refresh
                check_path: /token/refresh
                provider: main

        main:
            lazy: true

    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        - { path: ^/docs, roles: PUBLIC_ACCESS }
        - { path: ^/(login|token/refresh), roles: PUBLIC_ACCESS }
        - { path: ^/, roles: ROLE_USER }

服务.yaml

    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Kernel.php'

    Symfony\Component\Ldap\Ldap:
        arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
        tags: ['ldap']
    Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
        arguments:
            -   host: '%env(LDAP_HOST)%'
                port: '%env(LDAP_PORT)%'
                encryption: '%env(LDAP_ENCRYPTION)%'
                options:
                    protocol_version: 3
                    referrals: false

    App\Security\CustomUserProvider:
        arguments:
            $entityManager: '@doctrine.orm.entity_manager'
        tags:
            - { name: 'security.user_provider' }

    App\Security\CustomLdapAuthenticator:
        arguments:
            $ldap: '@Symfony\Component\Ldap\Ldap'
            $userProvider: '@App\Security\CustomUserProvider'
        tags: ['monolog.logger']

    App\Service\LdapCheck:
        bind:
            $ldapServiceDn: '%env(LDAP_BASE_DN)%'
            $ldapServiceUser: '%env(LDAP_SEARCH_DN)%'
            $ldapServicePassword: '%env(LDAP_SEARCH_PASSWORD)%'

如果我将 $token 转储到 onAuthenticationSuccess 函数中,这就是我得到的信息

CustomLdapAuthenticator.php on line 68:
Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken {#849 ▼
  -user: 
App\Entity
\
User {#810 ▼
    -id: 3
    -username: "john.doe"
    -roles: array:1 [▼
      0 => "ADDED_ROLE_IN_BDD_VIA_WORKER_SUBSCRIBER"
    ]
    -isActive: null
    -lastLogin: null
    -lastRefresh: null
  }
  -roleNames: array:3 [▼
    0 => "ADDED_ROLE_IN_BDD_VIA_WORKER_SUBSCRIBER"
    1 => "ROLE_USER"
    2 => "ENTITY_HARDCODED_ROLE"
  ]
  -attributes: []
  -firewallName: "api"
}

但是如果我解码 JWT

{
  "iat": 1733330446,
  "exp": 1733330491,
  "roles": [],
  "username": "john.doe"
}

在尝试了很多不同的事情之后,我承认我的头脑已经变得一团糟,可能在我的代码中也是如此。

感谢您的帮助

php symfony jwt ldap api-platform.com
1个回答
0
投票

你好,我不明白 这段代码

© www.soinside.com 2019 - 2024. All rights reserved.