我正在尝试使用 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"
}
在尝试了很多不同的事情之后,我承认我的头脑已经变得一团糟,可能在我的代码中也是如此。
感谢您的帮助
你好,我不明白 这段代码