AnythingLLM多用户管理与权限系统
文章详细介绍了AnythingLLM的多用户管理与权限系统,包括基于角色的访问控制(RBAC)实现、用户认证与会话管理机制、工作空间隔离与文档权限控制,以及管理员功能与系统配置管理。系统通过三个核心角色(管理员、经理、默认用户)实现细粒度权限管理,确保数据安全和灵活的用户管理。
基于角色的访问控制(RBAC)实现
AnythingLLM采用了一套精心设计的基于角色的访问控制(RBAC)系统,该系统通过三个核心角色(管理员、经理、默认用户)实现了细粒度的权限管理。这套系统不仅确保了多用户环境下的数据安全,还提供了灵活的用户管理和权限分配机制。
角色定义与权限层级
AnythingLLM的RBAC系统定义了三个明确的角色层级,每个角色都拥有特定的权限范围:
| 角色 | 权限描述 | 可管理对象 | 限制条件 |
|---|---|---|---|
| admin | 系统管理员,拥有最高权限 | 所有用户和工作空间 | 无限制 |
| manager | 经理角色,可管理普通用户 | 经理和默认用户 | 不能管理其他管理员 |
| default | 普通用户,基本使用权限 | 仅自己的资源 | 受工作空间访问限制 |
核心验证机制
1. 角色选择验证
系统通过validRoleSelection函数确保用户只能创建或修改权限等级不高于自己的用户:
function validRoleSelection(currentUser = {}, newUserParams = {}) {
if (!newUserParams.hasOwnProperty("role"))
return { valid: true, error: null };
if (currentUser.role === ROLES.admin) return { valid: true, error: null };
if (currentUser.role === ROLES.manager) {
const validRoles = [ROLES.manager, ROLES.default];
if (!validRoles.includes(newUserParams.role))
return { valid: false, error: "Invalid role selection for user." };
return { valid: true, error: null };
}
return { valid: false, error: "Invalid condition for caller." };
}
2. 管理员修改保护
canModifyAdmin函数防止系统陷入无管理员状态:
async function canModifyAdmin(userToModify, updates) {
if (!updates.hasOwnProperty("role")) return { valid: true, error: null };
if (userToModify.role !== ROLES.admin) return { valid: true, error: null };
if (updates.role === userToModify.role) return { valid: true, error: null };
const adminCount = await User.count({ role: ROLES.admin });
if (adminCount - 1 <= 0)
return {
valid: false,
error: "No system admins will remain if you do this. Update failed.",
};
return { valid: true, error: null };
}
3. 操作权限验证
validCanModify函数确保用户只能操作权限等级不高于自己的账户:
function validCanModify(currentUser, existingUser) {
if (currentUser.role === ROLES.admin) return { valid: true, error: null };
if (currentUser.role === ROLES.manager) {
const validRoles = [ROLES.manager, ROLES.default];
if (!validRoles.includes(existingUser.role))
return { valid: false, error: "Cannot perform that action on user." };
return { valid: true, error: null };
}
return { valid: false, error: "Invalid condition for caller." };
}
数据库模型设计
RBAC系统的核心建立在Prisma数据库模型之上,用户表包含角色字段:
model users {
id Int @id @default(autoincrement())
username String? @unique
password String
role String @default("default")
suspended Int @default(0)
// ... 其他字段
}
工作空间与用户的关联通过中间表实现:
model workspace_users {
id Int @id @default(autoincrement())
user_id Int
workspace_id Int
workspaces workspaces @relation(fields: [workspace_id], references: [id])
users users @relation(fields: [user_id], references: [id])
}
中间件权限验证
系统使用严格的中间件来保护API端点:
app.get(
"/admin/users",
[validatedRequest, strictMultiUserRoleValid([ROLES.admin, ROLES.manager])],
async (_request, response) => {
// 只有admin和manager可以访问用户列表
const users = await User.where();
response.status(200).json({ users });
}
);
strictMultiUserRoleValid中间件确保只有在多用户模式下且具有相应角色的用户才能访问受保护的路由:
function strictMultiUserRoleValid(allowedRoles = DEFAULT_ROLES) {
return async (request, response, next) => {
const multiUserMode = await SystemSettings.isMultiUserMode();
if (!multiUserMode) return response.sendStatus(401).end();
const user = await userFromSession(request, response);
if (allowedRoles.includes(user?.role)) {
next();
return;
}
return response.sendStatus(401).end();
};
}
工作空间访问控制
基于角色的工作空间访问控制通过getWithUser方法实现:
getWithUser: async function (user = null, clause = {}) {
if ([ROLES.admin, ROLES.manager].includes(user.role))
return this.get(clause); // 管理员可以访问所有工作空间
try {
// 普通用户只能访问自己被分配的工作空间
const workspace = await prisma.workspaces.findFirst({
where: {
...clause,
workspace_users: {
some: {
user_id: user?.id,
},
},
},
});
return workspace;
} catch (error) {
return null;
}
}
多用户模式检测
系统通过isMultiUserMode函数检测是否启用了多用户模式:
isMultiUserMode: async function () {
try {
const setting = await this.get({ label: "multi_user_mode" });
return setting?.value === "true";
} catch (error) {
return false;
}
}
权限验证流程
安全特性
- 角色继承保护:防止低权限用户提升自己或他人的权限等级
- 管理员保护:确保系统至少保留一个管理员账户
- 工作空间隔离:普通用户只能访问被明确授权的工作空间
- 操作审计:所有权限变更操作都被记录到事件日志中
- 会话验证:基于JWT的会话管理确保身份验证的安全性
这套RBAC实现为AnythingLLM提供了企业级的多用户管理能力,既保证了系统的安全性,又提供了灵活的用户权限管理方案。通过清晰的角色定义和严格的权限验证,确保了在多用户环境下各个用户只能访问其被授权的资源,有效防止了越权访问和数据泄露的风险。
用户认证与会话管理机制
AnythingLLM采用了一套完整且安全的用户认证与会话管理系统,支持多用户模式和单用户模式的无缝切换。该系统基于JWT(JSON Web Tokens)技术实现,结合了密码复杂度验证、会话管理、密码恢复和安全审计等功能。
认证体系架构
AnythingLLM的认证系统采用分层架构设计,确保安全性和灵活性:
用户模型与密码管理
用户模型定义了完整的用户属性和验证规则:
// 用户模型核心定义
const User = {
usernameRegex: new RegExp(/^[a-z0-9_\-.]+$/),
writable: ["username", "password", "pfpFilename", "role", "suspended", "dailyMessageLimit", "bio"],
validations: {
username: (newValue = "") => {
if (String(newValue).length > 100) throw new Error("用户名不能超过100字符");
if (String(newValue).length < 2) throw new Error("用户名至少2个字符");
return String(newValue);
},
role: (role = "default") => {
const VALID_ROLES = ["default", "admin", "manager"];
if (!VALID_ROLES.includes(role)) throw new Error("无效的角色");
return String(role);
}
}
};
密码复杂度验证采用可配置的策略:
| 配置参数 | 默认值 | 说明 |
|---|---|---|
| PASSWORDMINCHAR | 8 | 最小密码长度 |
| PASSWORDMAXCHAR | 250 | 最大密码长度 |
| PASSWORDLOWERCASE | 0 | 需要小写字母数量 |
| PASSWORDUPPERCASE | 0 | 需要大写字母数量 |
| PASSWORDNUMERIC | 0 | 需要数字数量 |
| PASSWORDSYMBOL | 0 | 需要特殊符号数量 |
| PASSWORDREQUIREMENTS | 0 | 需要满足的条件数量 |
JWT会话管理
会话令牌生成和验证流程:
// JWT令牌生成函数
function makeJWT(info = {}, expiry = "30d") {
if (!process.env.JWT_SECRET) throw new Error("JWT_SECRET未设置");
return JWT.sign(info, process.env.JWT_SECRET, { expiresIn: expiry });
}
// 会话令牌解码验证
function decodeJWT(jwtToken) {
try {
return JWT.verify(jwtToken, process.env.JWT_SECRET);
} catch {}
return { p: null, id: null, username: null };
}
认证中间件流程
认证中间件实现了双模式认证机制:
密码恢复系统
AnythingLLM实现了安全的密码恢复机制,采用双因素验证:
// 密码恢复代码生成
async function generateRecoveryCodes(userId) {
const newRecoveryCodes = [];
const plainTextCodes = [];
for (let i = 0; i < 4; i++) {
const code = v4(); // UUID v4生成
const hashedCode = bcrypt.hashSync(code, 10);
newRecoveryCodes.push({ user_id: userId, code_hash: hashedCode });
plainTextCodes.push(code);
}
// 存储哈希值并返回明文代码
}
密码恢复流程需要用户提供两个有效的恢复代码才能重置密码,确保安全性。
临时认证令牌
支持SSO集成的临时令牌机制:
const TemporaryAuthToken = {
expiry: 1000 * 60 * 6, // 6小时有效期
makeTempToken: () => {
const uuidAPIKey = require("uuid-apikey");
return `allm-tat-${uuidAPIKey.create().apiKey}`;
},
validate: async function (publicToken = "") {
// 验证令牌有效性并生成会话令牌
const sessionToken = makeJWT(
{ id: token.user.id, username: token.user.username },
process.env.JWT_EXPIRY
);
return { sessionToken, token, error: null };
}
};
安全审计与日志
所有认证事件都会被记录到事件日志中:
| 事件类型 | 记录内容 | 安全级别 |
|---|---|---|
| login_event | 登录成功事件 | 信息 |
| failed_login_invalid_username | 无效用户名尝试 | 警告 |
| failed_login_invalid_password | 无效密码尝试 | 警告 |
| failed_login_account_suspended | 暂停账户尝试 | 警告 |
| user_updated | 用户信息更新 | 信息 |
会话状态管理
会话状态检查端点确保前端能够验证令牌有效性:
app.get("/system/check-token", [validatedRequest], async (request, response) => {
if (multiUserMode(response)) {
const user = await userFromSession(request, response);
if (!user || user.suspended) {
response.sendStatus(403).end();
return;
}
response.sendStatus(200).end();
return;
}
response.sendStatus(200).end();
});
这套认证系统提供了企业级的安全保障,同时保持了良好的用户体验和可扩展性,支持从单用户部署到大规模多用户环境的平滑过渡。
工作空间隔离与文档权限控制
在AnythingLLM的多用户环境中,工作空间隔离与文档权限控制是确保数据安全和用户隐私的核心机制。该系统通过精细的权限模型和关系数据库设计,实现了不同用户对工作空间和文档的可控访问。
工作空间与用户关系模型
AnythingLLM采用多对多的关系模型来管理用户与工作空间之间的权限关系。每个工作空间可以包含多个用户,每个用户也可以访问多个工作空间,这种设计既保证了灵活性又确保了安全性。
权限控制实现机制
1. 用户角色与权限层级
AnythingLLM定义了清晰的用户角色体系,不同角色的用户对工作空间拥有不同的操作权限:
| 用户角色 | 工作空间访问权限 | 文档管理权限 | 用户管理权限 |
|---|---|---|---|
| 管理员 (Admin) | 所有工作空间 | 完全控制 | 完全控制 |
| 经理 (Manager) | 所有工作空间 | 完全控制 | 有限控制 |
| 成员 (Member) | 分配的工作空间 | 有限控制 | 无权限 |
2. 工作空间访问验证
系统在每次请求时都会验证用户对工作空间的访问权限:
// 权限验证核心代码示例
Workspace.getWithUser = async function (user = null, clause = {}) {
if ([ROLES.admin, ROLES.manager].includes(user.role))
return this.get(clause);
try {
const workspace = await prisma.workspaces.findFirst({
where: {
...clause,
workspace_users: {
some: {
user_id: user?.id,
},
},
},
include: {
workspace_users: true,
documents: true,
},
});
return workspace ? {
...workspace,
documents: await Document.forWorkspace(workspace.id),
contextWindow: this._getContextWindow(workspace),
currentContextTokenCount: await this._getCurrentContextTokenCount(workspace.id)
} : null;
} catch (error) {
return null;
}
};
3. 文档级权限控制
文档权限与工作空间权限紧密绑定,用户只能访问其有权访问的工作空间中的文档:
// 文档权限验证
Document.forWorkspace = async function (workspaceId) {
const documents = await prisma.documents.findMany({
where: { workspace_id: workspaceId },
include: { document_vectors: true }
});
return documents.map(doc => ({
...doc,
vectors: doc.document_vectors,
chunks: doc.document_vectors.length
}));
};
数据隔离实现
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



