feat: 完整实现AD域控用户认证系统

主要功能:
- 新增LDAP服务连接AD域控服务器
- 实现多格式AD用户认证(sAMAccountName, UPN, 域\用户名, DN)
- 支持中文显示名和拼音用户名搜索
- 添加用户账户状态检查(禁用账户检测)
- 实现JWT token认证和用户会话管理

新增文件:
- src/services/ldapService.js - LDAP核心服务
- src/routes/ldapRoutes.js - AD认证API路由
- src/services/userMappingService.js - 用户映射服务
- web/admin-spa/src/views/UserDashboardView.vue - 用户控制台
- web/admin-spa/src/components/user/ - 用户组件目录

修改功能:
- ApiStatsView.vue 增加用户登录按钮和模态框
- 路由系统增加用户专用页面
- 安装ldapjs和jsonwebtoken依赖

技术特性:
- 多种认证格式自动尝试
- LDAP referral错误处理
- 详细认证日志和错误码记录
- 前后端完整用户认证流程

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
iRubbish
2025-08-25 18:03:55 +08:00
parent 5c5548e839
commit 7624c383e8
12 changed files with 3037 additions and 4 deletions

View File

@@ -0,0 +1,195 @@
const logger = require('../utils/logger')
/**
* 用户映射服务 - 处理AD用户数据转换和过滤
*/
class UserMappingService {
/**
* 解析AD用户账户控制状态
*/
static parseUserAccountControl(uac) {
if (!uac) {
return { disabled: true, description: 'Unknown' }
}
const uacValue = parseInt(uac)
const flags = {
SCRIPT: 0x00000001,
ACCOUNTDISABLE: 0x00000002,
HOMEDIR_REQUIRED: 0x00000008,
LOCKOUT: 0x00000010,
PASSWD_NOTREQD: 0x00000020,
PASSWD_CANT_CHANGE: 0x00000040,
ENCRYPTED_TEXT_PASSWORD_ALLOWED: 0x00000080,
TEMP_DUPLICATE_ACCOUNT: 0x00000100,
NORMAL_ACCOUNT: 0x00000200,
INTERDOMAIN_TRUST_ACCOUNT: 0x00000800,
WORKSTATION_TRUST_ACCOUNT: 0x00001000,
SERVER_TRUST_ACCOUNT: 0x00002000,
DONT_EXPIRE_PASSWD: 0x00010000,
MNS_LOGON_ACCOUNT: 0x00020000,
SMARTCARD_REQUIRED: 0x00040000,
TRUSTED_FOR_DELEGATION: 0x00080000,
NOT_DELEGATED: 0x00100000,
USE_DES_KEY_ONLY: 0x00200000,
DONT_REQUIRE_PREAUTH: 0x00400000,
PASSWORD_EXPIRED: 0x00800000,
TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: 0x01000000,
PARTIAL_SECRETS_ACCOUNT: 0x04000000
}
const status = {
disabled: !!(uacValue & flags.ACCOUNTDISABLE),
locked: !!(uacValue & flags.LOCKOUT),
passwordExpired: !!(uacValue & flags.PASSWORD_EXPIRED),
normalAccount: !!(uacValue & flags.NORMAL_ACCOUNT),
passwordNotRequired: !!(uacValue & flags.PASSWD_NOTREQD),
dontExpirePassword: !!(uacValue & flags.DONT_EXPIRE_PASSWD),
description: this.getUserAccountControlDescription(uacValue)
}
return status
}
/**
* 获取用户账户控制的描述
*/
static getUserAccountControlDescription(uac) {
const uacValue = parseInt(uac)
if (uacValue & 0x00000002) {
return 'Account Disabled'
}
if (uacValue & 0x00000010) {
return 'Account Locked'
}
if (uacValue & 0x00800000) {
return 'Password Expired'
}
if (uacValue & 0x00000200) {
return 'Normal User Account'
}
return `UAC: ${uacValue}`
}
/**
* 过滤和映射AD用户数据
* 模拟Python代码中的get_ad()函数逻辑
*/
static mapAdUsers(searchResults) {
if (!Array.isArray(searchResults)) {
return []
}
// 移除第一个元素Python代码中的slist.pop(0)
const userList = searchResults.slice(1)
const mappedUsers = []
for (const user of userList) {
try {
const userObj = {
org: user.dn || user.distinguishedName,
cn: null,
userAccountControl: null,
accountStatus: null
}
// 提取CN
if (user.cn || user.CN) {
userObj.cn = user.cn || user.CN
} else {
// 如果没有CN属性跳过此用户
continue
}
// 提取userAccountControl
if (user.userAccountControl) {
userObj.userAccountControl = user.userAccountControl
userObj.accountStatus = this.parseUserAccountControl(user.userAccountControl)
} else {
// 如果没有userAccountControl跳过此用户
continue
}
mappedUsers.push(userObj)
} catch (error) {
logger.warn(`Error processing user entry: ${error.message}`, { user })
continue
}
}
return mappedUsers
}
/**
* 过滤活跃用户(未禁用的账户)
*/
static filterActiveUsers(users) {
return users.filter((user) => user.accountStatus && !user.accountStatus.disabled)
}
/**
* 根据用户名搜索(支持模糊匹配)
*/
static searchUsersByName(users, searchTerm) {
if (!searchTerm) {
return users
}
const term = searchTerm.toLowerCase()
return users.filter((user) => user.cn && user.cn.toLowerCase().includes(term))
}
/**
* 格式化用户信息用于显示
*/
static formatUserInfo(user) {
return {
name: user.cn,
distinguishedName: user.org,
accountControl: user.userAccountControl,
status: user.accountStatus
? {
enabled: !user.accountStatus.disabled,
locked: user.accountStatus.locked,
description: user.accountStatus.description
}
: null
}
}
/**
* 获取用户统计信息
*/
static getUserStats(users) {
const stats = {
total: users.length,
active: 0,
disabled: 0,
locked: 0,
passwordExpired: 0
}
users.forEach((user) => {
if (user.accountStatus) {
if (!user.accountStatus.disabled) {
stats.active++
}
if (user.accountStatus.disabled) {
stats.disabled++
}
if (user.accountStatus.locked) {
stats.locked++
}
if (user.accountStatus.passwordExpired) {
stats.passwordExpired++
}
}
})
return stats
}
}
module.exports = UserMappingService