mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
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:
195
src/services/userMappingService.js
Normal file
195
src/services/userMappingService.js
Normal 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
|
||||
Reference in New Issue
Block a user