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

Laravel应用无需默认账号密码实现LdapRecord认证

Laravel中LdapRecord+JWTAuth无需默认LDAP账号实现AD认证

场景与问题

基于Laravel开发的REST应用,使用LdapRecord PHP包对接Active Directory,通过JWTAuth实现JWT认证,目标是直接使用AD的用户名和密码完成登录认证。当前遇到的问题:当.env或LDAP配置文件中的默认账号密码设为null时,登录提示“用户名或密码无效”;但配置了默认账号密码时登录正常。需要实现无需依赖默认LDAP账号,仅通过已配置的Auth Guard和Provider完成用户认证。

已完成的配置代码

auth.php配置

'defaults' => [
        'guard' => 'ldap',
        'passwords' => 'users',
    ],

    'guards' => [
        // jwt with ldap auth
        'ldap' => [
            'driver' => 'jwt',
            'provider' => 'ldap',
        ],
    ],


    'providers' => [
        'ldap' => [
            'driver' => 'ldap',
            'model' => LdapRecord\Models\ActiveDirectory\User::class,
            'rules' => [],
            'scopes' => [],
            'database' => [
                'model' => App\Models\User::class,
                'sync_passwords' => false,
                'password_column' => false,
                'sync_attributes' => [
                    'email' => 'mail',
                    'username' => 'samaccountname',
                    'fname' => 'givenName',
                    'lname' => 'sn'
                ],
            ],
        ],
    ],

App\Models\User代码

class User extends Authenticatable implements LdapAuthenticatable, JWTSubject
{
    use AuthenticatesWithLdap;

    protected $table = 'users';
    protected $primaryKey = 'guid';
    public $incrementing = false;
    public $timestamps = false;

    protected $fillable = [];

    protected $hidden = ['guid'];

    protected $casts = [];

    /**
     * JWT - Functions
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }
    public function getJWTCustomClaims()
    {
        return [];
    }
}

登录控制器代码

public function login(Request $request) {
        // Check if username and password are passed
        $credentials = $request->only(['username', 'password']);
        if(!array_key_exists('username', $credentials) || !array_key_exists('password', $credentials) ) {
            return json_response(400, false, __('_.ERR_MISSING_CREDENTIALS'));
        }
        // Map username to samaccountname
        $credentials['samaccountname'] = $credentials['username'];
        unset($credentials['username']);
        // Check if auth is successful
        if(!$token = Auth::attempt($credentials)) {
            return json_response(401, false, __('_.ERR_WRONG_CREDENTIALS'));
        }
        return json_response(200, true, __('_.MSG_LOGGED_IN'))
           ->withCookie(cookie(config('_re.session_cookie_name'), $token, config('jwt.ttl')));
    }

解决方案

核心原因

LdapRecord默认采用查询绑定模式:先使用配置文件中的默认LDAP账号连接AD,查询匹配的用户条目,再验证用户输入的密码。若默认账号为空,这一步查询绑定会失败,导致无法找到用户,最终认证失败。我们需要改为直接用户绑定模式,即使用用户输入的账号密码直接连接AD完成认证,无需依赖默认服务账号。

具体实现步骤

1. 调整LDAP连接配置

修改config/ldap.php中的默认连接配置,将username和password设为空值,确保AD的基础连接信息(如hosts、base_dn)正确:

'connections' => [
    'default' => [
        'hosts' => [env('LDAP_HOST', 'dc.yourdomain.com')],
        'username' => env('LDAP_USERNAME', ''),
        'password' => env('LDAP_PASSWORD', ''),
        'base_dn' => env('LDAP_BASE_DN', 'dc=yourdomain,dc=com'),
        'port' => env('LDAP_PORT', 389),
        'use_ssl' => env('LDAP_USE_SSL', false),
        'use_tls' => env('LDAP_USE_TLS', false),
        // 保留其他默认配置
    ],
],

2. 修改登录控制器逻辑,实现直接绑定认证

替换原有登录函数,直接使用用户输入的凭证绑定AD,验证通过后同步用户信息并生成JWT:

use LdapRecord\Connection;
use LdapRecord\Auth\BindException;
use LdapRecord\Models\ActiveDirectory\User as LdapUser;

public function login(Request $request)
{
    // 验证输入参数
    $credentials = $request->validate([
        'username' => 'required|string',
        'password' => 'required|string',
    ]);

    try {
        // 根据AD环境选择绑定用户名格式,以下三种选其一:
        // 1. 使用samaccountname + 域名后缀(如user@yourdomain.com)
        $bindUsername = "{$credentials['username']}" . env('LDAP_USER_SUFFIX', '@yourdomain.com');
        // 2. 使用完整DN(如cn=用户名,ou=Users,dc=yourdomain,dc=com)
        // $bindUsername = "cn={$credentials['username']}," . env('LDAP_USER_OU', 'ou=Users,dc=yourdomain,dc=com');
        // 3. 直接使用samaccountname(部分AD支持)
        // $bindUsername = $credentials['username'];

        // 初始化LDAP连接
        $connection = Connection::make(config('ldap.connections.default'));

        // 尝试用用户凭证直接绑定AD
        $connection->bind($bindUsername, $credentials['password']);

        // 绑定成功后,查询用户LDAP信息(用于同步到本地数据库)
        $ldapUser = LdapUser::on($connection)
            ->where('samaccountname', $credentials['username'])
            ->firstOrFail();

        // 同步用户信息到本地数据库(按需调整字段)
        $localUser = \App\Models\User::updateOrCreate(
            ['guid' => $ldapUser->getConvertedGuid()],
            [
                'email' => $ldapUser->mail,
                'username' => $ldapUser->samaccountname,
                'fname' => $ldapUser->givenName,
                'lname' => $ldapUser->sn,
            ]
        );

        // 生成JWT Token
        $token = auth()->login($localUser);

        return json_response(200, true, __('_.MSG_LOGGED_IN'))
            ->withCookie(cookie(config('_re.session_cookie_name'), $token, config('jwt.ttl')));
    } catch (BindException $e) {
        // 账号密码错误
        return json_response(401, false, __('_.ERR_WRONG_CREDENTIALS'));
    } catch (\Exception $e) {
        // 用户不存在或其他异常
        return json_response(401, false, __('_.ERR_WRONG_CREDENTIALS'));
    }
}

3. 优化Auth Provider配置(可选)

保留原有auth.php中的LDAP Provider配置,确保sync_passwords和password_column保持false,避免本地数据库存储密码:

'providers' => [
    'ldap' => [
        'driver' => 'ldap',
        'model' => LdapRecord\Models\ActiveDirectory\User::class,
        'rules' => [],
        'scopes' => [],
        'database' => [
            'model' => App\Models\User::class,
            'sync_passwords' => false,
            'password_column' => false,
            'sync_attributes' => [
                'email' => 'mail',
                'username' => 'samaccountname',
                'fname' => 'givenName',
                'lname' => 'sn'
            ],
        ],
    ],
],

关键注意事项

  • 绑定用户名格式:AD支持多种绑定标识,需根据你的AD环境选择正确格式,建议优先使用userPrincipalName(即用户名+域名后缀)。
  • 错误处理:通过捕获BindException精准处理账号密码错误,其他异常统一返回认证失败,避免泄露敏感信息。
  • 用户同步:同步到本地数据库的操作可根据业务需求选择是否保留,若无需本地用户数据,可直接生成JWT(需确保Auth Guard能识别LDAP用户,但更推荐保留本地同步以兼容JWTAuth)。

内容的提问来源于stack exchange,提问作者noahshz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 04:22:34