From 09edb14d564f3082c77be56634b96e42fa818c96 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Wed, 15 Apr 2026 09:18:36 +0000 Subject: [PATCH] =?UTF-8?q?fix(ldap):=20=E4=BD=BF=E7=94=A8=20LDAP=20Bind?= =?UTF-8?q?=20=E8=AE=A4=E8=AF=81=E6=9B=BF=E4=BB=A3=20userPassword=20?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=EF=BC=8C=E5=85=BC=E5=AE=B9=20Active=20Direct?= =?UTF-8?q?ory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 认证方式从 userPassword 属性过滤改为标准 LDAP Bind,兼容所有 LDAP 服务器 - 新增可配置的登录属性(cn/uid/mail/sAMAccountName),AD 用户选 sAMAccountName 即可 - 移除 posixAccount objectClass,兼容 AD 目录结构 - 同步创建用户时移除 POSIX 专属属性,添加 mail 属性 - 用户查找改用 findByEmail 按 mail/cn/uid/userPrincipalName 依次匹配 - initConfig 从静态变量缓存改为 RequestContext 请求级缓存,修复 Swoole 下配置变更不生效的问题 - 默认登录属性为 cn,与旧版本行为一致,确保向后兼容 Co-Authored-By: Claude Opus 4.6 (1M context) --- app/Http/Controllers/Api/SystemController.php | 2 + app/Ldap/LdapUser.php | 128 +++++++++++++----- language/original-web.txt | 4 +- .../setting/components/SystemThirdAccess.vue | 9 ++ 4 files changed, 109 insertions(+), 34 deletions(-) diff --git a/app/Http/Controllers/Api/SystemController.php b/app/Http/Controllers/Api/SystemController.php index aac2733b6..53870bfff 100755 --- a/app/Http/Controllers/Api/SystemController.php +++ b/app/Http/Controllers/Api/SystemController.php @@ -612,6 +612,7 @@ class SystemController extends AbstractController 'ldap_password', 'ldap_user_dn', 'ldap_base_dn', + 'ldap_login_attr', 'ldap_sync_local' ])) { unset($all[$key]); @@ -625,6 +626,7 @@ class SystemController extends AbstractController // $setting['ldap_open'] = $setting['ldap_open'] ?: 'close'; $setting['ldap_port'] = intval($setting['ldap_port']) ?: 389; + $setting['ldap_login_attr'] = $setting['ldap_login_attr'] ?: 'cn'; $setting['ldap_sync_local'] = $setting['ldap_sync_local'] ?: 'close'; // return Base::retSuccess($type == 'save' ? '保存成功' : 'success', $setting ?: json_decode('{}')); diff --git a/app/Ldap/LdapUser.php b/app/Ldap/LdapUser.php index ba0d1e251..ab549afd5 100644 --- a/app/Ldap/LdapUser.php +++ b/app/Ldap/LdapUser.php @@ -4,6 +4,7 @@ namespace App\Ldap; use App\Models\User; use App\Module\Base; +use App\Services\RequestContext; use LdapRecord\Configuration\ConfigurationException; use LdapRecord\Container; use LdapRecord\LdapRecordException; @@ -11,7 +12,6 @@ use LdapRecord\Models\Model; class LdapUser extends Model { - protected static $init = null; /** * The object classes of the LDAP model. * @@ -22,9 +22,10 @@ class LdapUser extends Model 'organizationalPerson', 'person', 'top', - 'posixAccount', ]; + private static $emailAttrs = ['mail', 'cn', 'uid', 'userPrincipalName']; + /** * @return mixed|null */ @@ -68,19 +69,29 @@ class LdapUser extends Model return Base::settingFind('thirdAccessSetting', 'ldap_sync_local') === 'open'; } + /** + * 获取登录属性名 + * @return string + */ + public static function getLoginAttr(): string + { + $attr = Base::settingFind('thirdAccessSetting', 'ldap_login_attr'); + return in_array($attr, ['cn', 'uid', 'mail', 'sAMAccountName']) ? $attr : 'cn'; + } + /** * 初始化配置 * @return bool */ public static function initConfig() { - if (is_bool(self::$init)) { - return self::$init; + if (RequestContext::has('ldap_init')) { + return RequestContext::get('ldap_init'); } // $setting = Base::setting('thirdAccessSetting'); if ($setting['ldap_open'] !== 'open') { - return self::$init = false; + return RequestContext::save('ldap_init', false); } // $connection = Container::getDefaultConnection(); @@ -92,15 +103,15 @@ class LdapUser extends Model "username" => $setting['ldap_user_dn'], "password" => $setting['ldap_password'], ]); - return self::$init = true; + return RequestContext::save('ldap_init', true); } catch (ConfigurationException $e) { info($e->getMessage()); - return self::$init = false; + return RequestContext::save('ldap_init', false); } } /** - * 获取 + * 通过管理员绑定搜索用户,然后用用户 DN 做 Bind 认证 * @param $username * @param $password * @return Model|null @@ -111,16 +122,68 @@ class LdapUser extends Model return null; } try { - return self::static() - ->where([ - 'cn' => $username, - 'userPassword' => $password - ])->first(); + $loginAttr = self::getLoginAttr(); + $row = self::static() + ->whereRaw($loginAttr, '=', $username) + ->first(); + if (!$row) { + return null; + } + $connection = Container::getDefaultConnection(); + if (!$connection->auth()->attempt($row->getDn(), $password)) { + return null; + } + // Swoole 下连接共享,必须恢复管理员绑定 + $connection->auth()->attempt( + $connection->getConfiguration()->get('username'), + $connection->getConfiguration()->get('password') + ); + return $row; + } catch (\Exception $e) { + info("[LDAP] auth fail: " . $e->getMessage()); + return null; + } + } + + /** + * 通过邮箱查找 LDAP 用户 + * @param $email + * @return Model|null + */ + public static function findByEmail($email): ?Model + { + if (!self::initConfig()) { + return null; + } + try { + foreach (self::$emailAttrs as $attr) { + $row = self::static()->whereRaw($attr, '=', $email)->first(); + if ($row) { + return $row; + } + } + return null; } catch (\Exception) { return null; } } + /** + * 获取用户的邮箱(从 LDAP 记录中提取) + * @param Model $row + * @return string|null + */ + public static function getUserEmail(Model $row): ?string + { + foreach (self::$emailAttrs as $attr) { + $val = $row->getFirstAttribute($attr); + if ($val && Base::isEmail($val)) { + return $val; + } + } + return null; + } + /** * 登录 * @param $username @@ -138,7 +201,11 @@ class LdapUser extends Model return null; } if (empty($user)) { - $user = User::reg($username, $password); + $email = self::getUserEmail($row) ?: $username; + $user = User::whereEmail($email)->first(); + if (empty($user)) { + $user = User::reg($email, $password); + } } if ($user) { $userimg = $row->getPhoto(); @@ -173,7 +240,7 @@ class LdapUser extends Model } // if (self::isSyncLocal()) { - $row = self::userFirst($user->email, $password); + $row = self::findByEmail($user->email); if ($row) { return; } @@ -184,17 +251,18 @@ class LdapUser extends Model } else { $userimg = ''; } - self::static()->create([ + $attrs = [ 'cn' => $user->email, - 'gidNumber' => 0, - 'homeDirectory' => '/home/ldap/dootask/' . env("APP_NAME"), 'sn' => $user->email, 'uid' => $user->email, - 'uidNumber' => $user->userid, 'userPassword' => $password, 'displayName' => $user->nickname, - 'jpegPhoto' => $userimg, - ]); + 'mail' => $user->email, + ]; + if ($userimg) { + $attrs['jpegPhoto'] = $userimg; + } + self::static()->create($attrs); $user->identity = Base::arrayImplode(array_merge(array_diff($user->identity, ['ldap']), ['ldap'])); $user->save(); } catch (LdapRecordException $e) { @@ -205,11 +273,11 @@ class LdapUser extends Model /** * 更新 - * @param $username + * @param $email * @param $array * @return void */ - public static function userUpdate($username, $array) + public static function userUpdate($email, $array) { if (empty($array)) { return; @@ -218,10 +286,7 @@ class LdapUser extends Model return; } try { - $row = self::static() - ->where([ - 'cn' => $username, - ])->first(); + $row = self::findByEmail($email); $row?->update($array); } catch (\Exception $e) { info("[LDAP] update fail: " . $e->getMessage()); @@ -230,19 +295,16 @@ class LdapUser extends Model /** * 删除 - * @param $username + * @param $email * @return void */ - public static function userDelete($username) + public static function userDelete($email) { if (!self::initConfig()) { return; } try { - $row = self::static() - ->where([ - 'cn' => $username, - ])->first(); + $row = self::findByEmail($email); $row?->delete(); } catch (\Exception $e) { info("[LDAP] delete fail: " . $e->getMessage()); diff --git a/language/original-web.txt b/language/original-web.txt index 2912d2746..0bb815e77 100644 --- a/language/original-web.txt +++ b/language/original-web.txt @@ -2357,4 +2357,6 @@ AI任务分析 (*)和(*)等人的聊天记录 生日 -请选择生日 \ No newline at end of file +请选择生日 +登录属性 +用于匹配登录用户名的 LDAP 属性,Active Directory 请选择 sAMAccountName diff --git a/resources/assets/js/pages/manage/setting/components/SystemThirdAccess.vue b/resources/assets/js/pages/manage/setting/components/SystemThirdAccess.vue index 649509a0a..e413cf709 100644 --- a/resources/assets/js/pages/manage/setting/components/SystemThirdAccess.vue +++ b/resources/assets/js/pages/manage/setting/components/SystemThirdAccess.vue @@ -33,6 +33,15 @@ + + + uid + cn + mail + sAMAccountName + +
{{$L('用于匹配登录用户名的 LDAP 属性,Active Directory 请选择 sAMAccountName')}}
+
{{ $L('开启') }}