Symfony LDAP密码变更后旧密码仍可登录的安全问题修复咨询
Symfony LDAP(Active Directory)修改密码后新旧密码均可登录的问题解决
我在Symfony项目中通过LDAP(Active Directory)实现用户登录功能,但遇到一个安全隐患:服务器端修改LDAP密码后,短时间内使用新、旧密码都能成功登录。以下是我的security配置代码:
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: responsable de charger les utilisateurs à partir d'une source de données : ex : MySQL, LDAP... providers: ldap: ldap: service: Symfony\Component\Ldap\Ldap base_dn: 'DC=domain,DC=com' search_dn: '%env(LDAP_ADMIN_ACCOUNT)%' search_password: '%env(LDAP_PASSWORD)%' default_roles: ROLE_USER uid_key: sAMAccountName users_in_memory: { memory: null } # used to reload user from session & other features (e.g. switch_user) firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: lazy: true provider: ldap form_login_ldap: service: Symfony\Component\Ldap\Ldap login_path: app_login check_path : app_login dn_string: 'domain\{user_identifier}' enable_csrf: false username_parameter: login[username] password_parameter: login[password] logout: path: "logout" target: /login # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall # https://symfony.com/doc/current/security/impersonating_user.html # switch_user: 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: ^/admin, roles: ROLE_ADMIN } # - { path: ^/, roles: ROLE_USER } - { path: '^/', roles: PUBLIC_ACCESS, requires_channel: https } when@test: security: password_hashers: # By default, password hashers are resource intensive and take time. This is # important to generate secure password hashes. In tests however, secure hashes # are not important, waste resources and increase test times. The following # reduces the work factor to the lowest possible values. Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: algorithm: auto cost: 4 # Lowest possible value for bcrypt time_cost: 3 # Lowest possible value for argon memory_cost: 10 # Lowest possible value for argon
问题原因与解决办法
核心原因
这个问题主要来自两方面:
- Active Directory的密码同步/缓存机制:AD域控制器之间同步新密码需要时间,且部分环境会缓存旧密码几分钟用于兼容;
- Symfony的会话缓存:用户登录后,会话中会保留用户信息,默认不会每次请求都重新验证LDAP凭据。
具体解决方案
1. 禁用LDAP连接缓存
确保每次登录请求都直接访问AD验证最新密码,修改config/services.yaml配置LDAP服务时禁用缓存:
services: Symfony\Component\Ldap\Ldap: arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter'] Symfony\Component\Ldap\Adapter\ExtLdap\Adapter: arguments: - host: '%env(LDAP_HOST)%' port: 389 # 若用LDAPS则填636 encryption: tls # 根据你的实际配置调整 options: protocol_version: 3 referrals: false cache: false # 关键:禁用LDAP连接缓存
2. 强制每次请求重新验证(可选,适合高安全场景)
如果需要严格的即时验证,可以将防火墙设置为无状态,或者自定义认证器每次都验证凭据:
- 无状态模式(适合API,会禁用会话):
firewalls: main: # ... 现有配置 stateless: true
- 自定义认证器:创建
App\Security\LdapAuthenticator,在checkCredentials方法中直接绑定LDAP用户,确保每次登录都验证最新密码:
// src/Security/LdapAuthenticator.php namespace App\Security; use Symfony\Component\Ldap\LdapInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; class LdapAuthenticator extends AbstractGuardAuthenticator { private LdapInterface $ldap; public function __construct(LdapInterface $ldap) { $this->ldap = $ldap; } // ... 其他必要方法(start, getCredentials, getUser等) public function checkCredentials($credentials, UserInterface $user) { $dn = sprintf('domain\%s', $user->getUserIdentifier()); try { $this->ldap->bind($dn, $credentials['password']); return true; } catch (\Exception $e) { return false; } } }
3. 调整AD域密码策略
联系AD管理员,缩短旧密码缓存窗口,或者优化域控制器之间的密码同步频率,从源头减少延迟。
4. 登录后销毁旧会话
在登录成功后,强制销毁旧会话并创建新会话,避免旧会话被非法使用:
// 在登录控制器的成功处理逻辑中 use Symfony\Component\HttpFoundation\Session\SessionInterface; public function loginSuccess(SessionInterface $session) { // 销毁旧会话 $session->invalidate(); // 启动新会话 $session->start(); // 后续登录成功逻辑(如跳转、提示等) }
内容的提问来源于stack exchange,提问作者CalebDeCoteau
相关产品推荐
相关产品推荐

