SillyTavern 服务端架构深度分析
本文档深入分析 SillyTavern 项目的服务端架构,包括启动流程、Express 配置、路由系统、安全机制和多用户架构。
目录
1. 启动流程
1.1 启动流程 Mermaid 序列图
1.2 核心启动文件职责
| 文件 | 职责描述 |
|---|---|
| server.js | 入口文件,负责解析命令行参数,设置全局变量,导入主模块 |
| server-main.js | 核心启动文件,配置 Express 应用、中间件链、路由、执行预启动任务 |
| server-startup.js | 服务器启动工具类,处理 HTTP/HTTPS 服务器创建、IPv4/IPv6 双栈监听 |
1.3 启动阶段详解
Phase 1: 初始化阶段 (server.js)
// 1. 解析命令行参数
const cliArgs = new CommandLineParser().parse(process.argv);
globalThis.DATA_ROOT = cliArgs.dataRoot;
globalThis.COMMAND_LINE_ARGS = cliArgs;
// 2. 切换到服务器目录
process.chdir(serverDirectory);
// 3. 导入主模块
await import('./src/server-main.js');
Phase 2: 应用构建阶段 (server-main.js)
// 1. 创建 Express 实例
const app = express();
// 2. 配置中间件链(详见第2节)
app.use(helmet({ contentSecurityPolicy: false }));
app.use(compression());
// ... 更多中间件
// 3. 路由分层注册
app.use('/api/users', usersPublicRouter); // 公开 API
app.use(requireLoginMiddleware); // 认证边界
app.use('/', userDataRouter); // 私有 API
Phase 3: 预启动任务链
initUserStorage(dataRoot)
.then(setDnsResolutionOrder)
.then(ensurePublicDirectoriesExist)
.then(migrateUserData) // 数据迁移
.then(migrateSystemPrompts) // 系统提示词迁移
.then(verifySecuritySettings) // 安全验证
.then(preSetupTasks) // 插件加载等
.then(apply404Middleware)
.then(() => new ServerStartup(app, cliArgs).start())
.then(postSetupTasks);
2. Express 应用配置
2.1 中间件链 Mermaid 流程图
2.2 中间件详细配置
2.2.1 安全类中间件
| 中间件 | 用途 | 配置要点 |
|---|---|---|
| helmet | 安全响应头 | contentSecurityPolicy: false 禁用 CSP |
| cookie-session | 会话管理 | sameSite: 'lax', httpOnly: true, 动态 maxAge |
| csrf-sync | CSRF 保护 | 32字节 token,从 x-csrf-token header 读取 |
// Cookie Session 配置
app.use(cookieSession({
name: getCookieSessionName(), // session-{hostname_hash}
sameSite: 'lax',
httpOnly: true,
maxAge: getSessionCookieAge(), // 默认400天或配置值
secret: getCookieSecret(dataRoot), // 从文件读取或生成
}));
// CSRF 配置
const csrfSyncProtection = csrfSync({
getTokenFromState: (req) => req.session.csrfToken,
getTokenFromRequest: (req) => req.headers['x-csrf-token'],
storeTokenInState: (req, token) => req.session.csrfToken = token,
size: 32,
});
2.2.2 网络类中间件
| 中间件 | 用途 | 配置 |
|---|---|---|
| compression | Gzip 压缩 | 默认配置 |
| response-time | 响应时间统计 | 默认配置 |
| body-parser | 请求体解析 | limit: 500mb |
| cors | 跨域处理 | origin: 'null', methods: ['OPTIONS'] |
2.2.3 自定义安全中间件
// Basic Auth (config.yaml 配置)
if (cliArgs.listen && cliArgs.basicAuthMode) {
app.use(basicAuthMiddleware);
}
// IP 白名单
if (cliArgs.whitelistMode) {
const whitelistMiddleware = await getWhitelistMiddleware();
app.use(whitelistMiddleware);
}
// Host 白名单 (DNS Rebinding 防护)
app.use(hostWhitelistMiddleware);
2.3 请求处理流水线
┌─────────────────────────────────────────────────────────────────┐
│ Request Processing Flow │
├─────────────────────────────────────────────────────────────────┤
│ 1. Security Layer │
│ ├── helmet (安全头) │
│ ├── hostWhitelist (Host验证) │
│ └── basicAuth/whitelist (访问控制) │
│ │
│ 2. Parsing Layer │
│ ├── body-parser (请求体解析) │
│ ├── cors (跨域处理) │
│ └── cookie-session (会话初始化) │
│ │
│ 3. Context Layer │
│ ├── setUserDataMiddleware (用户数据注入) │
│ └── csrf-sync (CSRF验证) │
│ │
│ 4. Routing Layer │
│ ├── Static Assets (webpack + express.static) │
│ ├── Public API (无需认证) │
│ ├── Auth Middleware (认证检查) │
│ └── Private API (需要认证) │
│ │
│ 5. Error Handling │
│ └── 404 Middleware (未匹配路由) │
└─────────────────────────────────────────────────────────────────┘
3. 路由系统架构
3.1 路由分层结构
3.2 Public API 详细列表
| 路由 | 文件 | 功能 |
|---|---|---|
GET / | server-main.js | 主页,条件重定向到登录页 |
GET /login | server-main.js | 登录页面 |
GET /callback/:source | server-main.js | OAuth PKCE 回调 |
GET /csrf-token | server-main.js | 获取 CSRF 令牌 |
GET /version | server-main.js | 版本信息 |
/api/users/* | users-public.js | 用户公开接口 |
users-public.js 接口详情:
POST /api/users/list // 获取用户列表(支持 discreet 模式)
POST /api/users/login // 用户登录(带速率限制)
POST /api/users/recover-step1 // 密码恢复第一步(发送验证码)
POST /api/users/recover-step2 // 密码恢复第二步(验证并重置密码)
3.3 Private API 详细列表
3.4 废弃端点重定向
server-startup.js 中 redirectDeprecatedEndpoints 函数处理 API 版本迁移:
// 旧端点 → 新端点 (308 永久重定向)
/createcharacter → /api/characters/create
/renamecharacter → /api/characters/rename
/editcharacter → /api/characters/edit
/deletecharacter → /api/characters/delete
/getcharacters → /api/characters/all
/savechat → /api/chats/save
/getchat → /api/chats/get
// ... 更多重定向
4. 安全机制
4.1 多层安全防护架构
4.2 IP 白名单机制 (whitelist.js)
// 支持配置方式:
// 1. config.yaml whitelist 数组
// 2. whitelist.txt 文件
// 3. Docker 主机自动解析
function isIPInWhitelist(whitelist, ip) {
return whitelist.some(x => ipMatching.matches(ip, ipMatching.getMatch(x)));
}
// 处理 X-Real-IP / X-Forwarded-For 头
const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false);
4.3 Host 白名单机制 (hostWhitelist.js)
防止 DNS Rebinding 攻击:
const hostWhitelistEnabled = getConfigValue('hostWhitelist.enabled', false);
const hostWhitelist = getConfigValue('hostWhitelist.hosts', []);
const hostWhitelistScan = getConfigValue('hostWhitelist.scan', false);
// 扫描模式:记录未授权 Host,但不阻止
// 启用模式:严格验证 Host 头
4.4 Basic Auth 机制 (basicAuth.js)
支持两种模式:
- 全局 Basic Auth:单用户认证
- Per-User Basic Auth:多用户映射到 Basic Auth
const usePerUserAuth = PER_USER_BASIC_AUTH && ENABLE_ACCOUNTS;
if (!usePerUserAuth && username === basicAuthUserName && password === basicAuthUserPassword) {
return callback(); // 全局认证通过
} else if (usePerUserAuth) {
// 验证用户名对应用户密码
for (const userHandle of userHandles) {
if (username === userHandle) {
const user = await storage.getItem(toKey(userHandle));
if (user && user.enabled && user.password === getPasswordHash(password, user.salt)) {
return callback();
}
}
}
}
4.5 CSRF 保护机制
// Token 生成与验证流程
1. 客户端请求 GET /csrf-token
2. 服务端生成 token,存入 req.session.csrfToken
3. 客户端后续请求携带 x-csrf-token header
4. 服务端验证:req.session.csrfToken === req.headers['x-csrf-token']
// 禁用方式:--disableCsrf 命令行参数
// 禁用时返回固定 token: 'disabled'
4.6 安全验证流程 (verifySecuritySettings)
export async function verifySecuritySettings() {
// 监听模式安全检查
if (!listen) return; // localhost 模式跳过
// 强制安全配置检查
if (!ENABLE_ACCOUNTS) {
logSecurityAlert('Enable whitelisting, basic authentication or user accounts.');
}
// 用户密码检查
const unprotectedUsers = users.filter(x => !x.password);
const unprotectedAdminUsers = unprotectedUsers.filter(x => x.admin);
if (unprotectedAdminUsers.length > 0) {
logSecurityAlert('Set a password for all admin users.');
}
}
5. 多用户架构
5.1 用户数据隔离架构
5.2 用户目录结构 (USER_DIRECTORY_TEMPLATE)
| 目录键 | 路径 | 用途 |
|---|---|---|
root | {handle}/ | 用户根目录 |
characters | {handle}/characters/ | 角色卡片 |
chats | {handle}/chats/ | 个人聊天记录 |
groupChats | {handle}/group chats/ | 群组聊天记录 |
groups | {handle}/groups/ | 群组定义 |
worlds | {handle}/worlds/ | 世界信息 (World Info) |
backgrounds | {handle}/backgrounds/ | 背景图片 |
avatars | {handle}/User Avatars/ | 用户头像 |
extensions | {handle}/extensions/ | 用户扩展 |
assets | {handle}/assets/ | 资源文件 |
thumbnails | {handle}/thumbnails/ | 缩略图缓存 |
settings | {handle}/settings.json | 用户设置 |
secrets | {handle}/secrets.json | 密钥存储 |
5.3 用户模型
interface User {
handle: string; // 唯一标识,用于目录名
name: string; // 显示名称
created: number; // 创建时间戳
password: string; // scrypt 哈希值
salt: string; // 密码盐值
enabled: boolean; // 是否启用
admin: boolean; // 是否管理员
}
interface UserDirectoryList {
root: string;
characters: string;
chats: string;
// ... 20+ 目录路径
}
// 请求对象扩展
interface Request {
user?: {
profile: User;
directories: UserDirectoryList;
};
}
5.4 用户认证流程
5.5 自动登录机制
支持多种自动登录方式:
// 1. 单用户无密码自动登录
async function singleUserLogin(request) {
if (userHandles.length === 1) {
const user = await storage.getItem(toKey(userHandles[0]));
if (user && !user.password) {
request.session.handle = userHandles[0];
return true;
}
}
}
// 2. Authelia SSO 登录
async function autheliaUserLogin(request) {
return headerUserLogin(request, 'Remote-User');
}
// 3. Authentik SSO 登录
async function authentikUserLogin(request) {
return headerUserLogin(request, 'X-Authentik-Username');
}
// 4. Per-User Basic Auth 登录
async function basicUserLogin(request) {
// 从 Authorization header 提取用户名
// 验证密码后自动登录
}
5.6 用户数据中间件
export async function setUserDataMiddleware(request, response, next) {
// 禁用用户系统时使用默认用户
if (!ENABLE_ACCOUNTS) {
request.user = {
profile: DEFAULT_USER,
directories: getUserDirectories(DEFAULT_USER.handle),
};
return next();
}
// 从会话获取用户
const handle = request.session?.handle;
if (!handle) return next();
const user = await storage.getItem(toKey(handle));
if (!user || !user.enabled) return next();
request.user = {
profile: user,
directories: getUserDirectories(handle),
};
// 首页访问刷新会话
if (request.method === 'GET' && request.path === '/') {
request.session.touch = Date.now();
}
next();
}
5.7 多用户文件服务
// users.js 中的 Router 提供用户数据文件服务
export const router = express.Router();
router.use('/backgrounds/*', createRouteHandler(req => req.user.directories.backgrounds));
router.use('/characters/*', createRouteHandler(req => req.user.directories.characters));
router.use('/User%20Avatars/*', createRouteHandler(req => req.user.directories.avatars));
router.use('/assets/*', createRouteHandler(req => req.user.directories.assets));
router.use('/user/images/*', createRouteHandler(req => req.user.directories.userImages));
router.use('/scripts/extensions/third-party/*', createExtensionsRouteHandler(req => req.user.directories.extensions));
6. 核心模块交互关系
7. 总结
架构特点
- 分层清晰:启动流程、中间件、路由、安全机制分层明确
- 安全第一:多层安全防护,从网络层到应用层全面覆盖
- 多用户支持:完整的多用户隔离架构,支持多种认证方式
- 模块化设计:Endpoints 按功能模块化,易于扩展
- 向后兼容:废弃端点通过重定向保持兼容性
关键设计决策
| 决策 | 说明 |
|---|---|
| node-persist | 轻量级本地存储,适合单机部署 |
| cookie-session | 无服务端会话存储,水平扩展友好 |
| express.Router | 模块化路由,支持按功能拆分 |
| helmet (CSP disabled) | 允许内联脚本,适合前端框架 |
| 数据根目录隔离 | 所有用户数据与代码分离,便于备份和迁移 |