feat(ldap): 支持非邮箱用户名登录,完善 AD 兼容性

- 登录页放宽校验:登录模式允许任意账号格式,注册模式仍强制邮箱
- 登录属性新增 userPrincipalName 选项(AD 常用且通常是邮箱格式)
- LDAP 用户缺少邮箱属性时返回明确错误提示,替代误导性的"请输入正确的邮箱地址"
- LDAP 登录合并已有本地账号时记录 info 日志,便于审计

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kuaifan 2026-04-16 11:48:40 +00:00
parent e1c1fc030f
commit 1059630b9d
5 changed files with 18 additions and 4 deletions

View File

@ -2,6 +2,7 @@
namespace App\Ldap; namespace App\Ldap;
use App\Exceptions\ApiException;
use App\Models\User; use App\Models\User;
use App\Module\Base; use App\Module\Base;
use App\Services\RequestContext; use App\Services\RequestContext;
@ -76,7 +77,7 @@ class LdapUser extends Model
public static function getLoginAttr(): string public static function getLoginAttr(): string
{ {
$attr = Base::settingFind('thirdAccessSetting', 'ldap_login_attr'); $attr = Base::settingFind('thirdAccessSetting', 'ldap_login_attr');
return in_array($attr, ['cn', 'uid', 'mail', 'sAMAccountName']) ? $attr : 'cn'; return in_array($attr, ['cn', 'uid', 'mail', 'sAMAccountName', 'userPrincipalName']) ? $attr : 'cn';
} }
/** /**
@ -201,10 +202,15 @@ class LdapUser extends Model
return null; return null;
} }
if (empty($user)) { if (empty($user)) {
$email = self::getUserEmail($row) ?: $username; $email = self::getUserEmail($row);
if (empty($email)) {
throw new ApiException('LDAP 用户缺少邮箱属性,请联系管理员配置');
}
$user = User::whereEmail($email)->first(); $user = User::whereEmail($email)->first();
if (empty($user)) { if (empty($user)) {
$user = User::reg($email, $password); $user = User::reg($email, $password);
} elseif (!$user->isLdap()) {
info("[LDAP] merged with existing local account: userid={$user->userid}, email={$email}");
} }
} }
if ($user) { if ($user) {

View File

@ -973,3 +973,4 @@ AI 返回内容为空
此类型消息不支持转发 此类型消息不支持转发
没有权限操作此任务 没有权限操作此任务
请选择要转发的消息 请选择要转发的消息
LDAP 用户缺少邮箱属性,请联系管理员配置

View File

@ -2360,3 +2360,4 @@ AI任务分析
请选择生日 请选择生日
登录属性 登录属性
用于匹配登录用户名的 LDAP 属性Active Directory 请选择 sAMAccountName 用于匹配登录用户名的 LDAP 属性Active Directory 请选择 sAMAccountName
请输入帐号

View File

@ -507,11 +507,16 @@ export default {
this.code = $A.trim(this.code) this.code = $A.trim(this.code)
this.invite = $A.trim(this.invite) this.invite = $A.trim(this.invite)
// //
if (!$A.isEmail(this.email)) { if (this.loginType == 'reg' && !$A.isEmail(this.email)) {
$A.messageWarning("请输入正确的邮箱地址") $A.messageWarning("请输入正确的邮箱地址")
this.$refs.email.focus() this.$refs.email.focus()
return return
} }
if (!this.email) {
$A.messageWarning("请输入帐号")
this.$refs.email.focus()
return
}
if (!this.password) { if (!this.password) {
$A.messageWarning("请输入密码") $A.messageWarning("请输入密码")
this.$refs.password.focus() this.$refs.password.focus()

View File

@ -35,10 +35,11 @@
</FormItem> </FormItem>
<FormItem :label="$L('登录属性')" prop="ldap_login_attr"> <FormItem :label="$L('登录属性')" prop="ldap_login_attr">
<RadioGroup v-model="formData.ldap_login_attr"> <RadioGroup v-model="formData.ldap_login_attr">
<Radio label="uid">uid</Radio>
<Radio label="cn">cn</Radio> <Radio label="cn">cn</Radio>
<Radio label="uid">uid</Radio>
<Radio label="mail">mail</Radio> <Radio label="mail">mail</Radio>
<Radio label="sAMAccountName">sAMAccountName</Radio> <Radio label="sAMAccountName">sAMAccountName</Radio>
<Radio label="userPrincipalName">userPrincipalName</Radio>
</RadioGroup> </RadioGroup>
<div class="form-tip">{{$L('用于匹配登录用户名的 LDAP 属性Active Directory 请选择 sAMAccountName')}}</div> <div class="form-tip">{{$L('用于匹配登录用户名的 LDAP 属性Active Directory 请选择 sAMAccountName')}}</div>
</FormItem> </FormItem>