Hister 多用户模式完全指南:认证、用户管理、权限隔离与公共访问
2026/9/20 3:18:32 网站建设 项目流程
  • 搜索引擎
  • 全文检索
  • 后端
  • 前端
  • CLI

【免费下载链接】hister

Your own search engine

项目地址:https://gitcode.com/GitHub_Trending/hi/hister
点击查看免费下载

本指南基于 Hister 仓库中的官方文档 user-handling.md 编写,并结合仓库源码(config/config.go、server/model/user.go、cmd/users.go、server/session.go、server/server.go 等)对每一个配置项、命令与安全机制进行源码级印证与深入展开。读完本文,你将能够在一台 Hister 实例上启用多用户隔离,为团队成员创建账号、配置 OAuth 单点登录、管理个人访问令牌,并理解文档所有权与搜索作用域在底层是如何实现的。

一、功能总览:多用户如何共享同一实例

Hister 的多用户处理(User Handling)允许多个相互独立的用户共享单个 Hister 实例。开启后,每个用户拥有:

  • 独立的登录凭据(用户名 + 密码或 OAuth 身份);
  • 独立的文档集合与按作用域过滤的搜索结果;
  • 独立的个人访问令牌(Personal Access Token),供 API 客户端与 CLI 使用;
  • 独立的规则(Skip / Priority / Versioning)与搜索别名(Aliases)。

底层实现上,服务器共享同一套索引文件,但在搜索时强制执行文档所有权(ownership)。用户 ID 被写入每个文档索引记录,查询时按用户 ID 过滤(详见下文「文档隔离」一节)。

关键的设计取向是默认关闭

app: user_handling: false # 默认值,即单用户模式

由于用户处理默认关闭,Hister 保持完全向后兼容——现有单用户部署无需任何改动。已索引的文档存储在用户 ID0之下,在开启用户处理之后依然对所有已认证用户可见(见「单用户兼容」一节)。

在 config/config.go 中,app段的结构体字段与 YAML 键一一对应:UserHandling bool \yaml:"user_handling"`,与AccessTokenPublic` 等同级。

二、激活多用户模式

在配置文件的app段设置:

app: user_handling: true

注意:当user_handling生效时,app.access_token的含义发生变化——它不再作为全局客户端令牌,而仅用于将请求认证为对应 token 的用户。典型用法是:Hister 管理员在配置文件中把app.access_token设为自己的个人访问令牌,从而用命令行 Hister 命令以管理员身份执行操作(见 server/server.go 的populateUserContext:请求头携带的X-Access-Token会先尝试按用户 token 解析)。

开启后,先重启服务器,并至少创建一个用户账号,然后才能登录。创建用户的方法见下文「用户管理命令」。

三、认证机制

3.1 Web 界面登录与会话

启用用户处理后,Web 界面会向未登录访客展示登录页,输入用户名与密码即可登录。会话的底层机制在 server/session.go 中实现,要点如下:

  • Cookie 内容:仅包含一个随机的、不透明的会话标识符(32 字节随机数,Base64 编码),不包含任何用户数据;会话数据与标识符的哈希存储在 SQL 数据库中。
  • 过期策略:会话在最近一次有效请求后30 天过期sessionMaxAge,见 server/session.go);每次有效请求都会刷新会话与 Cookie 的过期时间,重新计算为距该请求 30 天。
  • Cookie 属性HttpOnly=trueSameSite=LaxSecure属性由server.base_url决定——当 base URL 使用 HTTPS 时启用,使用 HTTP 时禁用,因此本机回环(loopback)HTTP 实例仍可正常登录。
  • 令牌存储:数据库只保存会话令牌的SHA-256 哈希sessionTokenHash,见 server/session.go),数据库泄露不会直接暴露可用会话。

明文 HTTP 无法保护会话免受网络窃听,因此任何暴露到网络的实例都应使用 HTTPS。对Secure属性的判定可在 server/session.go 中看到:Secure: parsedBaseURL != nil && parsedBaseURL.Scheme == "https"

如果配置了 OAuth 提供方,登录页还会显示Sign in with <Provider>按钮(见下文 OAuth 登录)。

3.2 OAuth 登录

server.oauth配置了 GitHub、Google 或任意 OpenID Connect(OIDC)提供方时,用户可以通过 OAuth 登录,OAuth 账号无需密码。第一次通过 OAuth 登录时,Hister 会自动创建与之绑定的本地账号,用户名的来源因提供方而异(见 server/oauth 目录):

提供方用户名来源回退方案源码
GitHub登录名(logingithub.go
Google账号名称(name完整邮箱地址google.go
OIDCpreferred_username完整邮箱地址oidc.go

此后使用同一提供方身份登录会复用同一账号(通过OAuthID字段匹配,见 server/model/user.go 的GetUserByOAuthID/CreateOAuthUser)。

OAuth 账号与密码账号功能完全一致:拥有作用域内的文档与搜索结果、个人访问令牌、规则和别名。OAuth 用户可以在个人资料页(Profile)生成个人访问令牌,用于 CLI 或浏览器扩展。

配置方法见 configuration.md 的 OAuth 章节。配置校验逻辑在 config/config.go 的validateOAuth中:合法提供方名称为githubgoogleoidcclient_idclient_secret必填,OIDC 还需提供configuration_urlauth_url

3.3 OAuth-Only 模式

设置server.oauth_only: true彻底禁用密码登录,Web 界面只接受 OAuth 登录:登录页隐藏凭据表单,只显示 OAuth 提供方按钮。这适用于强制单点登录(SSO)策略、防止用户用本地设置的密码绕过 SSO 的场景。

oauth_only模式下个人访问令牌依然有效,因此 API 客户端和 CLI 工具无需浏览器登录即可完成认证。在多用户模式下,app.access_token作为客户端默认令牌,必须包含某个用户的个人令牌才能认证成功。

完整配置参考见 configuration.md 的 OAuth-Only 模式章节,配置结构体定义在 config/config.go(OAuthOnly bool \yaml:"oauth_only"``)。

3.4 浏览器扩展认证

浏览器扩展有两种认证方式:

  1. 个人访问令牌:在扩展弹窗(popup)或选项页(options page)中输入令牌并保存设置;
  2. 复制浏览器会话
    • 先在同一个浏览器中登录 Hister Web 界面;
    • 点击扩展弹窗或选项页中的Authenticate with Browser Session按钮;
    • 扩展会复制 Web 界面当前活跃的会话 Cookie。

所有经扩展索引的页面都会存入该用户的账号之下(文档归属由认证用户 ID 决定,见「文档隔离」)。

3.5 API 与命令行客户端认证

任何 API 客户端都可以通过X-Access-Token请求头携带个人访问令牌完成认证:

curl -H "X-Access-Token: <your-token>" http://localhost:4433/api/stats

Hister CLI 使用-t标志传入令牌:

hister -t <your-token> search "query"

在源码层面,令牌认证有两套路径:请求头X-Access-Token,以及Authorization: Bearer <token>形式(见 server/server.go 的requestAccessToken)。认证后,populateUserContext会依据令牌查询用户并填充用户上下文(server/server.go)。

四、用户管理命令

所有用户管理命令都要求user_handling: true,并且需要能直接访问服务器主机(它们会直接更新用户数据库,因此不应在远程通过 API 暴露)。其中delete-user还会联系正在运行的 Hister 服务器,搜索该用户拥有的文档,并在提供--purge时通过 API 删除它们。命令实现在 cmd/users.go 中,对应的数据库操作在 server/model/user.go 中。

4.1 create-user

创建新用户账号,交互式提示输入密码(最少 8 个字符,需输入两次确认)。

hister create-user USERNAME [--admin]
标志说明
--admin授予该用户管理员权限

底层实现:model.CreateUser使用bcrypt(默认成本)对密码加盐哈希后存储,并生成随机访问令牌(server/model/user.go)。密码在 cmd/users.go 的promptConfirmedPassword中完成长度校验(少于 8 字符直接报错)与两次输入一致性校验,并通过交互式 TUI 以*回显输入。

4.2 delete-user

软删除用户账号。若服务器发现该用户拥有已索引的文档,除非提供--purge,否则命令拒绝继续--purge会在软删除账号前,先删除预检(preflight)发现的文档。需要注意:这不是完整的数据擦除,关于多用户所有权与数据保留的细节见>hister delete-user USERNAME [--purge]

标志说明
--purge先删除该用户名下找到的已索引文档,再删除账号

源码逻辑(cmd/users.go):命令先用user_id:<ID>查询该用户的文档数,若Total > 0且未提供--purge则直接退出并提示;提供--purge时调用DeleteDocuments清理文档,最后执行model.DeleteUser软删除。

4.3 show-user

显示用户账号信息。

hister show-user USERNAME [--token]
标志说明
--token同时显示该用户的访问令牌(默认隐藏)

示例输出:

Username: alice ID: 1 Admin: yes Created at: 2026-03-26 09:00:00 Updated at: 2026-03-26 09:00:00

4.4 update-user

修改既有用户账号,至少必须提供一个标志

hister update-user USERNAME [--username NEW] [--password] [--regen-token] [--toggle-admin]
标志说明
--username NEW将用户名改为NEW
--password交互式提示并设置新密码
--regen-token生成并打印新访问令牌,旧令牌立即失效
--toggle-admin切换管理员状态(开/关)

标志可以组合使用;当--username与其他标志一起使用时,先执行改名(cmd/users.go 中先调用model.UpdateUsername再继续后续标志处理)。密码同样要求至少 8 个字符并输入两次确认。未提供任何标志时,命令会报错no changes specified退出(cmd/users.go)。

五、每用户规则与别名

开启用户处理后,每个用户的规则与别名独立存储于数据库(不是配置文件)。通过 Web UI 或 API 的修改只影响当前认证用户的规则,不会改动配置文件。

  • Skip rules(跳过规则):命中用户跳过规则的 URL 在索引时被静默忽略,与单用户模式行为一致;
  • Priority rules(优先规则):用户的优先规则将匹配结果提升到其搜索结果顶部;
  • Versioning rules(版本化规则):命中版本化规则的 URL 在每次重新索引时对其内容做 diff 并保存;
  • Search aliases(搜索别名):用户定义的别名仅作用于该用户的搜索。

用户可以通过 Web 界面的Rules标签页或 API 端点查看和编辑自己的规则与别名。而单用户模式下,规则与别名继续从应用数据目录下的rules.json读写。

底层实现:用户模型中的RulesJSON字段以 JSON 形式存储规则(默认'{}'),ParseRules在读取时反序列化并补齐缺失的 Skip/Priority/Versioning 与 Aliases 字段,然后调用Compile()编译正则;SaveUserRules将规则序列化写回数据库(server/model/user.go)。在请求处理中,webContext.effectiveRules()会优先返回认证用户的规则(server/server.go)。

六、规则正则语法(Regexp)

规则正则的匹配行为对实际配置至关重要,以下是官方文档明确的行为约定:

  • 跳过规则作用于完整的 URL(从协议到查询字符串参数),并且匹配范围是受限的;
  • 锚定必须包含协议:例如^https://foo.com^https?://(login|mail)\.是合法的,而^foo.com无法命中;
  • /login$不会匹配https://foo.com/login?auth=1(因为 URL 还带有查询字符串);
  • URL 中的 hash 会被移除https://foo.com/#active-tab归一化为https://foo.com/
  • 查询字符串参数不会被重排,仅剥离utm_*参数;
  • Go 正则表达式(regexp/syntax语法)不支持 look-ahead / look-behind(前瞻/后瞻)断言

这些约定在规则编译与 URL 归一化流程中生效(见 config/config.go 的Rules结构与规则编译逻辑),编写规则时务必留意。

七、管理员用户

管理员(Admin)用户拥有特权操作权限。目前以下端点要求管理员权限:

  • POST /api/reindex:重建整个全文搜索索引;
  • POST /api/cleanup:移除不再匹配所配置目录的本地文档,并删除没有任何当前文档引用的已存 HTML 与 favicon 文件。

非管理员用户调用仅管理员端点会收到403 Forbidden。端点到权限的映射在 server/endpoints.go 中实现:当userHandling开启且端点需要认证时,AdminOnly端点套用withAdminAuth,其余套用withUserAuth;而 server/server.go 的withAdminAuth会检查UserID == 0(未登录)与IsAdmin两个条件,任一不满足即返回 403。

管理员状态可在创建时授予(create-user --admin),也可随时切换(update-user --toggle-admin,底层为 server/model/user.go 的ToggleAdmin)。另外,多用户模式下管理员可通过X-Hister-Target-User-ID请求头以其他用户身份执行操作(server/server.go 的targetUserID)。

八、单用户兼容与存量数据迁移

Hister保留用户 ID0用于未认证(匿名)场景。未开启用户处理时索引的文档存储于用户 ID0之下,开启功能后这些文档仍然对所有已认证用户可见。这意味着你可以在既有实例上开启用户处理,而不会丢失对之前索引内容的访问。

若想把既有的全局文档收归某个特定用户私有,流程如下:

  1. hister show-user USERNAME找到该用户的数字 ID;
  2. 以管理员身份执行查询更新:
hister update 'user_id:0' --user-id USER_ID

几点注意事项:

  • 先加--dry预览受影响的文档数量;
  • 当该用户已经拥有相同 URL 时,所有权冲突会被跳过;
  • 被监视的本地文件(watched local files)需要目录配置中的user值与新所有者一致——该配置字段见 config/config.go 的Directory.User,文件索引队列会依据它确定归属用户(server/indexer/files.go 的directoryUserID)。

九、文档隔离与搜索作用域

每个用户的已索引文档都携带其用户 ID 存储。搜索自动限定在以下范围内:

  • 由当前已认证用户索引的文档;
  • 未开启用户处理时索引的文档(用户 ID0),它们作为共享的只读基线,对所有用户可见。

用户之间无法看到彼此的文档。首页显示的文档计数反映的是当前认证用户自己的文档数,而非所有用户的总数。

源码佐证:查询构建在 server/indexer/history.go 的latestDocumentsQuery中,当userID > 0时为查询附加user_id字段过滤,否则走全局查询;写入侧 server/indexer/files.go 的IndexFile接收userID参数并写入文档记录。单文件索引时以GetByURLAndUser(fileURL, userID)区分归属,确保同一 URL 在不同用户下可独立存在。

十、公共模式(Public Mode)

app.public: true与用户处理同时启用时:

  • 匿名访客只能搜索用户 ID0下的全局文档;具名用户拥有的文档对匿名访客保持私有,仅对各自已认证用户可见;
  • 已认证用户依然可以按正常规则添加、删除、打标签、管理自己的内容,并访问自己的 Web 历史;
  • Web 历史对匿名访客不可用(源码见 server/server.go 的historyEnabled!c.Config.App.Public || c.Authenticated)。

配置合法性校验在 config/config.go 的ValidatePublicMode:启用public时必须同时配置app.access_tokenapp.user_handling,否则报错app.public requires app.access_token or app.user_handling

十一、个人访问令牌

每个用户账号都有一个用于 API 认证的个人访问令牌。令牌是随机生成的,存储在数据库中(server/model/user.go 使用crypto/randrand.Text()RegenerateToken同理)。

  • 从 Web UI 生成:Profile → Generate Token;命令行:hister update-user --regen-token
  • 生成新令牌会立即作废旧令牌——记得同步更新所有客户端(浏览器扩展、脚本);
  • show-user默认不显示令牌,需要--token标志才会揭示。

令牌认证路径在 server/model/user.go 的GetUserByToken中按明文 token 精确匹配数据库记录;为降低泄露风险,建议将令牌视为机密,妥善保管并在可能泄露时及时重新生成。

十二、安全考量汇总

  • 密码:使用bcrypt加盐哈希后存储(默认成本),任何 API 都不会返回密码(User.Passwordjson:"-"标签,server/model/user.go);
  • 浏览器 Cookie:只包含随机会话标识符;会话数据与标识符哈希存储在配置的 SQL 数据库中;登出会立即撤销数据库中的会话记录
  • 个人访问令牌:绕过会话 Cookie,可用于脚本。请保密保存,泄露后立即重新生成;
  • OAuth state 令牌:单次使用的随机值,存储在服务端会话中,用于防止 OAuth 重定向流程中的跨站请求伪造(CSRF);
  • OAuth 登录默认使用 S256 PKCE:私有 verifier 将授权请求绑定到其令牌交换,与 state 和提供方信息一起存储在服务端会话中。老旧提供方的兼容配置与升级行为见 configuration.md;
  • OAuth 账号没有密码:管理员可用hister update-user USERNAME --password为其分配密码;如需停用某个 OAuth 用户的访问,用hister delete-user删除账号,或从配置中移除对应提供方;
  • 强制 OAuth:启用server.oauth_only: true可禁止密码认证;个人访问令牌对 API 与 CLI 依然有效。多用户模式下app.access_token必须包含某个用户的个人令牌;
  • 适用场景边界:用户处理面向同一实例上的可信用户群体(家庭、团队)。对公网部署,应将 Hister 置于带 HTTPS 的反向代理之后,并且只索引允许公开展示的内容。

十三、快速上手清单

  1. 在配置文件的app段设置user_handling: true,按需配置server.oauth(可选)与app.public
  2. 重启 Hister 服务器;
  3. 在服务器主机上执行hister create-user <USERNAME>(需要管理员就用--admin)创建首个账号;
  4. 通过 Web 界面用用户名/密码或 OAuth 登录,并在 Profile 页生成个人访问令牌;
  5. hister -t <your-token> search "query"curl -H "X-Access-Token: <your-token>" ...验证 API 认证;
  6. 按需使用update-user --toggle-admin/--regen-token管理账号,用delete-user --purge彻底清理用户及其文档。

至此,你已经掌握了 Hister 多用户模式从配置、认证、命令管理到文档隔离与安全的完整体系。更进一步,可以结合 configuration.md 的 OAuth 与 PKCE 章节、data-lifecycle.md 的多用户所有权章节,以及 cmd/users.go 与 server/model/user.go 的源码继续深入。

  • 搜索引擎
  • 全文检索
  • 后端
  • 前端
  • CLI

【免费下载链接】hister

Your own search engine

项目地址:https://gitcode.com/GitHub_Trending/hi/hister
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询