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
相关产品推荐
相关产品推荐

