用 Rauthy 管理自建服务的身份入口
在把 Matrix、Forgejo、Crow CI、GoToSocial 这些服务逐步部署到服务器上之后,一个很自然的问题就出现了:每个服务都有自己的一套账号,但每个服务都自己管理账号并不一定是一件好事。
如果只是一个服务,内置账号系统当然最简单。但服务一多,账号、密码、二次验证、注册开关、用户禁用、权限变更都会分散到不同后台里。对个人自建服务来说,这种复杂度来自系统数量和不同服务本身的管理逻辑区别。于是我开始需要一个单独的身份入口,把这些应用从“各管各的账号”逐渐收敛成“应用负责自己的业务,身份服务负责登录和认证”,并且用同一套身份入口提供相对统一的身份验证强度。
我目前选择的工具是 Rauthy。它不像 Keycloak 那样强调企业级目录、复杂组织结构和大量集成能力,而是更像一个适合个人、小团队和轻量服务的 OIDC 身份提供者。
几个相关概念
在部署身份服务之前,最容易混在一起的几个词是 OAuth2、OIDC 和 IdP。它们经常一起出现,但解决的问题并不完全相同。
OAuth2 主要解决的是授权问题。一个典型例子是:某个应用想访问你在另一个服务里的部分资源,比如读取头像、读取邮箱、访问某个 API。OAuth2 不要求你把密码交给这个应用,而是通过授权服务器签发一个有范围和时效限制的 token。这个 token 通常只代表“允许这个客户端做某些事”,并不直接等价于“这个人是谁”。
OIDC,也就是 OpenID Connect,是建立在 OAuth2 之上的身份层。它在授权流程之上补了一套标准化的“登录”和“身份声明”机制,比如 id_token、userinfo、issuer、subject、claims 等概念。一个应用接入 OIDC 之后,就可以把“用户是谁、邮箱是什么、显示名是什么、这个登录是不是可信的”交给外部身份服务处理。
IdP 是 Identity Provider,也就是身份提供者。对自建服务来说,它就是统一登录入口。用户先在 IdP 里完成登录,应用再信任 IdP 返回的身份信息。Forgejo、Crow CI、Matrix 或者 GoToSocial 这类服务只要支持 OIDC,就可以把账号入口委托给同一个 IdP。
所以这三个词放在一起,大致可以这样理解:
- OAuth2 负责授权,让客户端拿到访问权限。
- OIDC 在 OAuth2 之上负责认证,让客户端知道登录者是谁。
- IdP 是提供认证能力的服务本身。
在这个关系里,Rauthy 扮演的就是 IdP。它给其他应用签发 token、提供用户信息、管理登录流程,并且尽量让这些事情以标准的 OIDC/OAuth2 方式发生。
Rauthy 是什么
Rauthy 官方把自己描述成一个通过 OpenID Connect、OAuth2 和 PAM 提供单点登录的身份与访问管理系统。简单说,它就是一个自托管 SSO 服务:用户在 Rauthy 登录一次,然后通过 OIDC 接入不同应用。
它比较吸引我的地方,是目标非常明确。它不是一个试图复制完整企业身份平台的庞大系统,而是把重点放在轻量部署、安全默认值、Passkey、管理界面和常见协议支持上。默认情况下,Rauthy 可以使用自己的嵌入式 Hiqlite 存储,也可以改用 PostgreSQL。对于我这种已经有 PostgreSQL 容器的部署环境来说,后者很好接;对于只想少维护一个数据库的人来说,默认方案也足够直接。
从原理上看,Rauthy 夹在用户和应用之间:
- 用户访问 Forgejo、Crow CI 或其他支持 OIDC 的应用。
- 应用把用户重定向到 Rauthy 的授权端点。
- Rauthy 完成登录、MFA 或 Passkey 验证。
- Rauthy 把授权码返回给应用。
- 应用用授权码换取 token,再根据 token 和
userinfo确认用户身份。
这个过程的好处是,应用不需要自己处理密码,也不需要自己实现 Passkey。它只要信任 Rauthy 这个 issuer,并且正确校验 token,就能得到一套相对标准的登录能力。
Rauthy 的功能范围也不只限于网页应用。它支持 OIDC/OAuth2,支持 OAuth2 Device Authorization Grant 这类更适合 CLI 或无头设备的登录方式,也提供 PAM/NSS 相关模块用于 Linux 登录场景。不过对我的自建服务来说,最核心的还是 OIDC。
Passkey 的要求和取舍
Rauthy 对 Passkey 的强调很明显。它支持传统的“密码 + 安全密钥”模式,也支持 Passkey-only 账号。后者意味着账号可以没有密码,登录时使用邮箱和 FIDO2 Passkey 完成验证。
但 Passkey-only 不是随便一个 WebAuthn 设备都能满足。Rauthy 要求这类 Passkey 能提供 User Verification,也就是用户验证。实际体验里,这通常意味着平台密码钥匙、带 PIN 或生物识别能力的安全密钥,或者能确认“这个操作确实由当前用户发起”的认证器。只有安全密钥本身在场,但不能验证使用者身份,并不够构成完整的 Passkey-only 登录。
这里有一个容易忽略的细节:Rauthy 不鼓励 discoverable credentials。也就是说,它倾向于让用户先输入邮箱,再用对应的 Passkey 登录,而不是让认证器自己枚举可用账号。这样做的结果是,登录时多了一步邮箱输入,但安全密钥上的存储槽位压力会小很多。对 YubiKey 这类设备来说,这一点很实际,因为不同型号、不同固件版本能保存的 discoverable passkey 数量并不是无限的。
Passkey 的部署还受到浏览器和 TLS 的限制。生产环境里应该使用正常可信的 HTTPS 证书;本地测试时,如果使用自签名证书,有些浏览器可能不允许注册 Passkey,或者需要把测试 CA 加到信任列表。身份服务不是普通内部工具,登录入口如果一开始就依赖临时证书、错误域名和浏览器例外,后面很容易把问题藏进一堆“不知道为什么这台设备不能登录”的边角里。
不过我现在也不觉得 Passkey 会马上替代所有二次验证方式。至少在实际使用里,更普遍的仍然是 Google Authenticator、Aegis、1Password、Bitwarden Authenticator 这类 TOTP 服务。它们没有 Passkey 那么抗钓鱼,也依赖共享密钥和时间窗口,但胜在兼容性强、迁移成本低、几乎所有服务都支持。对自建服务来说,TOTP 仍然是最现实的 MFA 基线;Passkey 更像是值得逐步引入的高强度登录方式。
所以我的态度是:Rauthy 的 Passkey 能力值得用,但不应该一开始就把所有账号都推到 Passkey-only。更稳妥的方式是先用密码加 TOTP 或安全密钥跑通主要服务,再逐步把自己的主账号迁到 Passkey-only。身份服务一旦不可用,影响的是整套自建系统,而不是某个单独应用。
为什么需要它
我现在自建服务的方向越来越像一个小型个人基础设施,而不是几个互相独立的玩具服务。
Matrix 负责聊天和通知,Forgejo 负责代码托管,Crow CI 负责构建流水线,GoToSocial 负责联邦社交。下一步我还希望把 Vaultwarden 接进来,用来保存 Passkey、TOTP seed 和一些不常用但不能丢的凭据。它们各自都能创建账号,也各自都有权限和用户资料。但如果每个系统都单独维护账号,几个问题会很快出现。
第一是注册入口不可控。Matrix 可以有一套注册策略,Forgejo 又有一套,GoToSocial 还有一套。只要有一个服务忘了关注册,或者配置和预期不一致,就可能暴露出一个不想开放的入口。
第二是账号生命周期分散。添加一个用户,要分别去多个后台操作;停用一个用户,也要记得每个系统都处理一遍。对公开服务来说,这涉及安全;对私人服务来说,这至少也是维护负担。
第三是二次验证和密码策略难以统一。有些服务支持 Passkey,有些只支持 TOTP,有些支持得并不好。把登录入口统一到 Rauthy 之后,至少可以让主要的认证策略集中在一个地方,而不是跟着每个应用的实现质量走。
第四是未来扩展更简单。今天是 Matrix、Forgejo、Crow CI 和 GoToSocial,明天可能是 Vaultwarden、MinIO、Grafana、Paperless、Nextcloud 或者其他内部工具。只要新服务支持 OIDC,接入成本就会比较稳定:创建 client,设置 redirect URI,配置 scopes,确认 claim 映射,然后把应用里的登录入口指向 Rauthy。
这也是我选择 Rauthy 的核心原因。它不只是“又部署了一个登录页面”,而是把后续自建服务的认证方式抽成了一层公共基础设施。这样每加一个服务,不需要重新思考一次账号系统。
和 Cloudflare Access 的组合
还有一种值得单独拿出来说的用法:不要让每个应用都直接接 Rauthy,而是让 Cloudflare Access 先接 Rauthy,再把一部分 Web 服务放到 Cloudflare 的代理和 Access 规则后面。
Cloudflare One 支持 Generic OIDC 身份提供者。按 Cloudflare 的文档 配置时,需要先在 Rauthy 里给 Cloudflare 创建一个 OIDC client,回调地址填成 Cloudflare Access 的 callback:
https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/callback 然后在 Cloudflare Zero Trust 的 Identity providers 里选择 OpenID Connect,把 Rauthy OIDC discovery 里对应的 authorization endpoint、token endpoint、JWKS endpoint,以及 client ID 和 client secret 填进去。这样访问被 Cloudflare Access 保护的域名时,请求会先被 Cloudflare 拦住;用户跳到 Rauthy 完成登录和 MFA 之后,Cloudflare 再按 Access policy 判断是否放行到后端服务。
从效果上看,这有点像一个 Rauthy Forward Auth:那些没有内置 OIDC 支持、或者我不想直接暴露登录入口的 Web 服务,可以先交给前置认证层保护。区别在于这一层由 Cloudflare Access 承担,而不是由我在反向代理里自己拼一套 forward-auth 逻辑。这样它和 Cloudflare Tunnel、Access policy、审计日志、设备状态、会话策略以及 Gateway policy 的关系更自然,Rauthy 则继续专注做上游 IdP。
这个组合对 Cloudflare One Client 也更友好。对于需要通过 Cloudflare One Client 访问的私有网页应用,可以复用同一个 Rauthy 身份源和同一套 Access 策略:用户是否已经登录、会话是否过期、设备是否满足策略,都可以在 Cloudflare 这一层统一处理。直接接入 Rauthy 的应用仍然可以继续走自己的 OIDC 登录;不适合改造的内部面板、临时工具和管理入口,则可以先放在 Cloudflare Access 后面。
这不是要用 Cloudflare 替代 Rauthy。更准确地说,Rauthy 仍然负责“这个用户是谁”,Cloudflare Access 负责“这个请求能不能进到这个入口”。对个人自建服务来说,这个边界很好用:核心应用可以直接信任 Rauthy,边缘入口和无 OIDC 应用则交给 Cloudflare 做前置门禁。
和现有服务的关系
不同服务接 OIDC 的方式不完全一样,但思路大致类似。
Forgejo 这类代码托管平台适合把 Rauthy 作为 OAuth2/OIDC 登录源。这样用户可以通过统一身份入口进入代码平台,而 Forgejo 仍然负责仓库权限、组织、issue、pull request 等业务权限。
Crow CI 的关系更适合作为 Forgejo 之后的延伸,而不是直接变成另一个独立的身份入口。现在的流程更像是:Rauthy 先给 Forgejo 提供登录,Crow CI 再依靠 Forgejo 做认证和仓库授权。这样 Crow CI 的用户身份可以复用 Forgejo,而一个 Forgejo 用户也可以再链接一个 GitHub 账号。这个设计对小型个人自建服务很重要,因为它不要求我为了使用 Crow CI,就把所有代码和历史项目都立刻迁移到 Forgejo。Forgejo 可以先成为自建控制面,GitHub 上暂时还没迁走的内容也可以逐步接入流水线。
Matrix 的情况更复杂一些。我原来使用的是 Synapse 自带的账号和管理方式,但如果要把上游身份交给 Rauthy,就需要迁移到 Matrix Authentication Service。MAS 本身是 Matrix 正在迁移到 OIDC 认证层时使用的 OAuth2/OIDC 服务,它可以作为 Matrix 客户端和 homeserver 之间的认证层,也可以通过 upstream_oauth2.providers 接到 Rauthy 这样的上游 OIDC Provider。
这一步迁移会遇到一个很典型的“鸡生蛋蛋生鸡”问题:Rauthy 里要先创建 MAS 这个 OIDC client,MAS 又要正确配置自己的 issuer、redirect URI、homeserver 共享密钥和数据库;Synapse 切换到 MAS 之后,登录和注册入口会发生变化,但管理员账号、已有用户、客户端登录状态和回滚路径都必须提前想好。也就是说,不能只把它当作“在 Synapse 配置里加一个 OIDC provider”。真正的链路是 Rauthy 管上游身份,MAS 管 Matrix 的 OIDC 认证层,Synapse 再和 MAS 通过共享密钥和兼容接口配合。
GoToSocial 的需求则更像“社区入口”。联邦社交服务的账号不是普通后台账号,它对外就是身份本身。这里接入 OIDC 的意义不只是少记一个密码,还包括把加入、禁用和认证策略放到一个更靠前的位置。
Vaultwarden 则是后续最需要谨慎处理的一个。它很适合保存 Passkey、TOTP seed、恢复码、备用密码和一些低频凭据,但它和 Rauthy 之间天然存在启动顺序问题:如果 Vaultwarden 的登录依赖 Rauthy,而 Rauthy 的恢复凭据又只存在 Vaultwarden 里,那么一旦任意一边初始化失败或者证书、回调地址、邮件配置出问题,就会把自己锁在系统外面。更合理的做法是先保留离线恢复材料和至少一个 break-glass 账号,再考虑让 Vaultwarden 接入统一身份。
这些服务并不会因为接入 Rauthy 就失去自己的权限模型。Rauthy 负责“你是谁”和“你能不能登录”,应用仍然负责“你在这个应用里能做什么”。这个边界很重要。如果试图把所有应用内部权限都强行塞进 IdP,最后会把身份服务做成另一个复杂平台。
不足和现实问题
Rauthy 很适合我现在这种轻量自建场景,但它不是没有问题。
目前最明显的不足,是注册策略还不够细。它支持关闭注册,也支持开放注册,还支持一定程度的注册域名限制。但对个人服务或者小社区来说,真正需要的往往是中间状态:邀请码注册、申请后管理员审批、只允许已有用户邀请、注册后先进入待审核状态,或者按 client 区分不同注册策略。
域名限制对企业邮箱或者学校邮箱有用,但对个人域名、小型社区和朋友之间的服务并不总是合适。完全开放注册风险太高,完全关闭注册又会让新用户只能靠管理员手动创建。夹在中间的这些流程,恰好是自建服务最常遇到的需求。
管理员能力也偏弱。Rauthy 有 Admin UI,也能管理用户、客户端、事件和一些安全选项,但和成熟企业 IdP 相比,它的管理员工作流还比较朴素。比如批量操作、审核队列、更细的邀请流程、更明确的用户生命周期状态、更强的审计筛选和恢复工具,都不是它现在最强的地方。
这并不意味着 Rauthy 不可用。相反,对我来说,它的轻量和安全默认值比复杂后台更重要。但它确实更像一个强认证核心,而不是完整的社区用户管理系统。如果要把它用于公开注册的服务,仍然需要谨慎设计外围流程,甚至可能要额外写一层邀请或审批工具。
目前的定位
我现在对 Rauthy 的期待很克制:先让它成为自建服务的统一登录入口,而不是一开始就让它承担所有用户管理想象。
短期内,它负责几件事:
- 统一 Matrix、Forgejo、Crow CI、GoToSocial 等服务的登录入口。
- 集中管理我的主账号、安全密钥和 Passkey。
- 为后续新增服务提供稳定的 OIDC 接入方式。
- 作为 Cloudflare Access 这类前置访问控制的上游 IdP。
- 尽量减少每个应用各自保存密码的需求。
长期来看,如果它的注册和管理员能力继续增强,它也许可以成为更完整的个人身份中枢。但即使只停留在现在这个阶段,它已经解决了一个很实际的问题:当自建服务越来越多时,身份不应该继续散落在每个应用的角落里。
Rauthy 的价值不在于它替代了所有账号系统,而在于它给这些系统提供了一个共同入口。对于个人自建服务来说,这已经是从“几个单独容器”走向“可维护基础设施”的关键一步。
评论系统尚未配置。请在 .env 中填写 giscus 所需的环境变量。