You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题原因与解决办法

核心原因

这个问题主要来自两方面:

  1. Active Directory的密码同步/缓存机制:AD域控制器之间同步新密码需要时间,且部分环境会缓存旧密码几分钟用于兼容;
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 05:25:17