自托管开源软件为什么要收 SSO 税?
开源应用可免费自托管,却将 OIDC 和 SAML 放进付费层。了解维护者的定价逻辑,以及安装前核对 IdP、用户计费和替代方案的清单。
SSO 税是什么
自托管软件中的 SSO 税,是指应用本身免费,但单点登录(SSO)成为唯一需要付费购买的功能。您可以在自己的 VPS 上运行完整应用,不需要许可证密钥,也不限制用户数量。然后,您打开文档中的身份验证页面,却发现 OpenID Connect(OIDC)或 SAML(安全断言标记语言)被列在付费方案下。
这比普通的付费功能更重要,因为 SSO 能让一组自托管服务像一个系统一样运行。身份提供商(IdP)为每个人提供一个账户、一套密码策略、一个启用多因素身份验证(MFA)的位置,以及一个停用用户的位置。没有 IdP 时,每个应用都会维护自己的小型用户数据库,您必须逐个手动管理。
这种模式已经存在很久,甚至有公开的统计名单。sso.tax 上的 SSO Wall of Shame 列出了对单点登录收取高额溢价的厂商,条目可追溯到 2018 年。其作者设定了一个合理标准:“如果您的 SSO 支持只导致价格上涨 10%,您就不会出现在这份名单中。”这份名单中的大多数是闭源软件。如今,采用相同定价逻辑的也包括由您自行托管的开源项目。
为什么维护者会将单点登录放在付费层级
原因有两个,而且都很直接。SSO 的支持成本很高,同时它也是大型组织少数愿意付费购买的功能之一。
支持成本确实存在,因为身份集成永远不会真正完成。每个 IdP 对 claims 的格式处理都略有不同。群组映射、会话生命周期、重定向 URL 和时钟偏差都可能导致登录故障,而登录故障会同时锁定所有用户,因此相关工单必须紧急处理。随后还会出现一系列后续需求:嵌套群组、角色映射、使用 SCIM(跨域身份管理系统)进行自动配置,以及合规团队需要查阅的审计日志。
收入方面是算术问题,而不是恶意行为。无法将应用连接到自有 IdP 的公司根本不会部署该应用,因此 SSO 清楚地区分了付费用户和非付费用户。采用开放核心模式的项目必须在某处划出这条界线。与几乎所有其他功能相比,SSO 更适合充当这条界线,这就是许多项目选择它的原因。
需要纠正一个常见的说法:在假设某项功能被移除之前,先查看变更日志,因为功能移除会出现在发布说明中。我检查了本文涉及的项目,其中付费 SSO 功能从一开始就是为付费层级构建的。我没有发现任何已正常运行的免费 SSO 被撤下的案例。Grafana 就是典型例子。其 SAML 页面有一行说明:“可用于 Grafana Enterprise 和 Grafana Cloud”;而针对您自己的 issuer 使用通用 OAuth,则可在开源版本中使用。
SSO 机制的实际成本
费用只是较小的一部分。付费层按用户收费,因此团队规模越大,账单越高,而托管和升级仍由您负责。
更大的成本是手动处理身份管理,主要体现在四个方面。
- 每个应用都要单独保存密码,因此一个被复用的密码一旦泄露,所有共用该密码的应用都会受到影响。
- 依靠记忆执行离职处理。您必须记住某人使用过的每项服务,而忘记处理的那一项往往最关键。
- 每个应用都要单独配置 MFA,而且并非所有应用都支持 MFA。
- 共享登录凭据。在这种压力下,小团队实际上经常会这样做。
最后一点值得单独说明。当团队在文档管理器中共享一个管理员帐户时,审计记录中的所有操作都显示为同一个名称,因此您无法判断是谁删除了发票。按用户分配的权限也无法正常工作,因为系统中实际上只有一个用户。这就是 SSO 机制实际造成的损害:它会让小团队转而使用一个共享帐户,而这比其他任何替代方案都更糟糕。
采用任何应用前应执行的检查清单
在执行 docker compose up 之前运行此检查,而不是等应用已经保存 400 份文档后再检查。
- 打开文档中的身份验证页面,阅读顶部的套餐说明。付费功能通常会带有标记,或附有一行可用性说明。
- 确认应用通过 OIDC 或 SAML 连接到您自己的身份提供方,而不是只能连接到固定的公共提供方列表。
- 检查角色和组映射。创建用户只是工作的一半;在 10 个应用中手动分配权限,才是最繁琐的部分。
- 检查应用是否接受受信任代理在请求头中传递的已认证用户名,以及您是否可以明确指定它信任的代理。
- 阅读 git 中的许可证历史,并检查贡献者是否签署 CLA(贡献者许可证协议)。
- 检查用户离职后的处理流程。确认 IdP 账户被禁用后,API(应用程序编程接口)令牌和活动会话会如何处理。
大多数失望都发生在第 2 项。 “使用 Google 登录”按钮并不表示应用支持使用您的身份提供方进行 OIDC;它只是与一个供应商的固定集成。真正的支持会要求您提供 issuer URL,其他信息都会通过发现机制获取。您可以使用一条命令确认身份提供方是否支持这一功能。
curl -s https://id.example.com/.well-known/openid-configuration \
| jq '.issuer, .authorization_endpoint, .token_endpoint'正常的身份提供方会返回 3 个 URL。结果为空或返回 404,通常表示发现路径错误;该路径因身份提供方而异:Keycloak 将其发布在 /realms/<realm>/.well-known/openid-configuration 下。如果应用根本没有用于填写 issuer URL 的字段,那么无论功能列表如何描述,它都无法连接到您的 IdP。
第 6 项通常要到人员离职数周后才会暴露问题。在 IdP 中禁用账户会阻止新的登录,但不会撤销应用之前签发的 API 令牌,因为应用会自行验证该令牌,从不向 IdP 查询令牌状态。因此,用户离职处理分为两步:先在 IdP 中禁用账户,然后在每个应用中删除该用户或其令牌。
实际的层级徽章说明
这些信息均已根据各项目自身的文档在 August 2026 进行核对。先看付费方案。
Grafana 将 SAML 标注为 “Available in Grafana Enterprise and Grafana Cloud”,团队同步和 SCIM 配置也包含在其中。通用 OAuth、GitHub OAuth、LDAP(轻量级目录访问协议)和 auth proxy 都包含在开源版本中,因此小型自托管环境仍可通过自己的身份提供商登录。付费限制点在 SAML,而不是整个单点登录功能。这是 “SSO tax” 一词容易掩盖的细节。
Metabase 的说明更加直接。其文档写道:“SAML authentication is only available on Pro and Enterprise plans (both self-hosted and on Metabase Cloud).” 开源版本保留密码登录和 LDAP。
Passbolt 将其 SSO 文档标记为 Pro 和 Cloud,因此社区版不提供该功能。文档列出的身份提供商包括 Keycloak 和 Entra ID。
现在看另一种情况,因为这种模式并不普遍。
- GitLab Self-Managed 的 SAML 页面标注为 “Tier: Free, Premium, Ultimate”,因此针对自己的 GitLab 使用 SAML 不需要付费。
- Paperless-ngx 通过 django-allauth 和
PAPERLESS_SOCIALACCOUNT_PROVIDERS配置 OIDC,通过PAPERLESS_DISABLE_REGULAR_LOGIN隐藏本地登录表单,并通过PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS将 claims 映射到组。 - Planka 接受
OIDC_ISSUER、OIDC_CLIENT_ID和OIDC_CLIENT_SECRET,将作用域默认为openid profile email,并通过OIDC_ADMIN_ROLES中的角色 claim 提升管理员权限。 - BookStack 通过
AUTH_METHOD=oidc切换到该身份验证方式,然后使用OIDC_USER_TO_GROUPS=true和OIDC_GROUPS_CLAIM将身份提供商的组映射到自身角色。 - Vaultwarden 在 1.35.0 中于 27 December 2025 发布了 “support for SSO with OpenID Connect”,该功能来自一名贡献者提交的 pull request;此前该贡献者一直在 fork 中维护此功能。
- listmonk 从 v4.0.0 起就同时支持 OIDC 登录和自身的用户角色。
在选择产品时就使用这些信息,不要等到已经确定方案后才查看。一个 Planka 看板 和其他 自托管 Trello 替代品 对身份验证的处理方式并不相同,BookStack、Wiki.js 和 Outline 也是如此。免费 OIDC 可以像存储限制或移动客户端一样纳入权衡。如果你还在制定候选列表,2026 年适合自托管的服务 是一个合理的起点;Paperless-ngx 文档管理器 和 Vaultwarden 目前都免费提供 OIDC。
为什么应用前的反向代理不是单点登录
常见的解决方法是 forward auth。反向代理会暂存每个请求,向身份验证服务询问当前浏览器是否已登录,只有通过验证后才将请求转发给应用。authentik 将其称为 proxy provider,并提供适用于单个应用的 forward auth 模式,以及适用于整个域的另一种模式。Authelia 和 oauth2-proxy 也执行相同的任务。
下面是一个 Caddy site block,写法遵循 authentik 自己的示例。
app.example.com {
forward_auth http://authentik-outpost:9000 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Email X-Authentik-Groups
trusted_proxies private_ranges
}
reverse_proxy app:8000
}在 Caddy 中,这些标头名称的大小写很重要,因为名称不匹配时,传入的值会为空。对于每个获准的请求,outpost 都会设置 X-authentik-username、X-authentik-email、X-authentik-groups 以及其他几个标头。
这能带来以下效果。任何人都必须先通过您的身份提供商,才能访问应用。因此,未修补的登录表单不再直接暴露在互联网中,MFA 也会一次应用于反向代理后的所有内容。
但它不能提供应用内部的身份。应用仍然拥有自己的账户,也有自己判断登录用户的方式。如果所有人都通过反向代理,然后进入同一个共享管理员账户,那么您拥有的是一道强大的入口,以及其后的一个匿名会话。审计日志仍然只显示一个名称。不同用户之间仍然无法使用不同的权限。将这种配置称为 SSO 是一种安全错误,因为注销流程只完成了一半:从您的 IdP 中移除用户可以关闭入口,但用户在应用内创建的 API token 仍会对任何能够直接访问应用的人有效。
确保请求头身份验证安全
有些应用支持从反向代理获取用户名,因此无需付费 SSO 即可识别每个用户。不同项目中的配置名称各不相同。
Grafana 将其称为 auth proxy,默认禁用。请求头名称默认为 X-WEBAUTH-USER,也可以将其指向反向代理设置的任意请求头。
[auth.proxy]
enabled = true
header_name = X-authentik-username
header_property = username
auto_sign_up = true
whitelist = 10.0.0.5whitelist 是最容易被忽略的配置项。Grafana 文档明确说明,该配置用于防止用户伪造请求头,因此其中只能填写反向代理的地址,不能包含其他地址。Gitea 也支持相同功能,但配置名称不同,并且默认值更安全。
[security]
ENABLE_REVERSE_PROXY_AUTHENTICATION = true
REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
REVERSE_PROXY_TRUSTED_PROXIES = 10.0.0.5/32
REVERSE_PROXY_LIMIT = 1REVERSE_PROXY_TRUSTED_PROXIES 默认为 127.0.0.0/8,::1/128,REVERSE_PROXY_LIMIT 表示 Gitea 在代理链中信任的代理数量。将该限制设为零会完全关闭请求头处理。
Paperless-ngx 通过 PAPERLESS_ENABLE_HTTP_REMOTE_USER 和 PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME 提供此功能,其文档中包含适用于所有这些配置的警告:
只需向请求添加 Remote-User: <username> 请求头即可完成身份验证。请谨慎使用!
以下两条规则可以确保请求头身份验证安全,且都与可访问性有关。首先,除了通过反向代理外,应用不得以其他方式访问,因为任何能够与应用建立 socket 连接的人都可以发送该请求头,并伪装成任意用户。在 Docker 中,ports: ["8000:8000"] 会在所有网络接口上发布端口,因此应使用 ports: ["127.0.0.1:8000:8000"] 将其绑定到 loopback 地址,或者取消发布端口,并让反向代理加入同一 Docker 网络。其次,反向代理必须删除客户端发送的同名请求头,这样应用看到的唯一值才会是反向代理完成身份验证后设置的值。
检查这两点。第一条命令在 VPS 外部的计算机上运行,第二条命令在服务器本机运行。
curl -si -H "Remote-User: admin" http://203.0.113.10:8000/ | head -n 1
ss -ltnp | grep 8000curl 应连接失败,ss 应输出 127.0.0.1:8000,而不是 0.0.0.0:8000。如果第一行输出为 HTTP/1.1 302 Found,说明应用正在直接响应公网请求,因此任何人都可以在请求头中指定用户名,以任意用户身份登录。
没有免费的 SSO 时,应做出选择,而不是抱怨
以下 4 个选项按我的尝试顺序排列。
- 选择支持 OIDC 的应用。如果两个项目功能相同,而其中一个可免费对接您的身份提供商,这会实际影响运行成本。
- 坦诚地使用 forward auth。对于只有一个账户和一名操作员的管理工具,在前面部署代理就足够了。在应用内部提供每个用户的身份信息没有实际收益。
- 付费。如果应用是您工作流程的核心,按席位计费的价格也符合团队规模,那么这笔钱可以帮助项目持续维护;否则,替代方案就是占用您晚上的时间。
- 先搜索 issue tracker,再向上游提出请求。Vaultwarden 的 OIDC 支持来自贡献者的 fork 和一个长期存在的 pull request。因此,如果功能请求背后有可用的实现,有时确实可以进入免费版本。
如果没有您自己的身份提供商,以上方案都无法工作,因此应先构建这一部分。在 VPS 上运行 authentik 可为您提供 OIDC 和 SAML 提供商,以及上文使用的 forward auth outpost;如果您还不想立即确定方案,Keycloak、authentik 和 Zitadel 对比介绍了各方案的权衡。
许可证历史,以及它为何列入检查清单
最后一项检查面向未来,因为今天的层级布局只是一个快照。两个记录完善的案例表明,情况可能快速向两个方向变化。HashiCorp 于 2023 年 8 月 10 日对所有未来版本采用 Business Source License 1.1,而更早的版本仍采用 MPL 2.0(Mozilla Public License)。Redis 于 2024 年 3 月改用 SSPL(server side public license),随后于 2025 年 5 月 1 日宣布 Redis 8 也采用 AGPLv3(GNU Affero General Public License)。
应将这两个案例视为机制方面的证据,而不是动机方面的证据。您今天看到的许可证适用于您今天安装的版本;如果项目持有全部版权,就可以自行更改下一版本的许可条款。这就是 CLA 问题列入检查清单的原因,因为广泛的版权转让正是单方面重新授权成为可能的前提。
在基于某个项目构建系统之前,请自行检查其历史。
git clone --filter=blob:none https://github.com/paperless-ngx/paperless-ngx.git
cd paperless-ngx
git log --follow --oneline -- LICENSE提交记录较短,且大多来自首次导入,通常是积极信号。如果许可证文件经过多次改写,您应先阅读每次提交消息,再根据当前条款制定方案。
FAQ
什么是 SSO 税?
SSO 税是指将单点登录作为高级功能收费,而产品的其他部分免费或价格较低。在自托管软件中,这通常表现为:应用本身无需许可证密钥即可运行,但 OIDC 或 SAML 登录被放在付费层级中。这个名称源自 sso.tax 上的 SSO Wall of Shame,该页面记录了对单点登录收取高额溢价的供应商。对自托管用户来说,后果是每个应用都保留自己的用户数据库,因此必须手动创建和删除账户。
带有 forward auth 的反向代理等同于 SSO 吗?
不等同。forward auth 保护的是入口:代理会先向身份提供商验证,再让请求到达应用。后端应用仍使用自己的账户,因此如果所有人都进入同一个共享登录,就只有一个匿名会话,审计日志中也只会显示一个名称。只有当应用读取请求头中的用户名时,才会获得真正的按用户身份识别能力;Grafana 的 auth proxy、Gitea 的反向代理身份验证和 Paperless-ngx 的 PAPERLESS_ENABLE_HTTP_REMOTE_USER 都支持此功能。只有在应用无法绕过代理直接访问时,这些设置才是安全的,因为请求头只是普通字符串,任何客户端都可以发送。
哪些自托管应用在免费版本中包含 OIDC?
根据 2026 年 8 月查阅的项目文档,Paperless-ngx、Planka、BookStack、Gitea、listmonk 和 Vaultwarden 的免费构建版本都支持 OIDC,GitLab Self-Managed 则将 SAML 列为 Tier: Free。Grafana 的开源构建版本支持通过通用 OAuth 连接到您自己的 issuer,而 SAML 在 Grafana 中属于 Enterprise 功能。安装前请确认项目自己的身份验证页面,因为这些列表会随版本发布而变化。
我应该购买解锁单点登录的版本吗?
用两个数字来决定:需要账户的人数,以及如果不使用单点登录,您需要手动维护的应用数量。对于一两名管理员,在本地账户前使用 forward auth 已经足够,付费版本带来的价值很小。对于人员经常加入和离开的团队,离职时漏删一个账户造成的损失可能高于许可证费用,而这笔费用也用于支持您所依赖的维护工作。如果价格不合适,实际做法是选择包含 OIDC 的应用,而不是设法绕过不支持 OIDC 的应用。