通过 OpenID Connect 实现单点登录

现在,用户可以用你身份提供商(IdP)里已有的账号登录 Specivo。Specivo 是一个 OpenID Connect 客户端,因此 Keycloak、authentik、Microsoft Entra ID 以及任何发布了发现文档(discovery document)的提供商都可以接入。在 管理 → 单点登录 中完成配置;在你添加提供商之前,单点登录(SSO)一直处于关闭状态。

我们用 Keycloak 和 authentik 做了端到端测试。Entra ID 有一页完整的配置说明。Okta、Google、Zitadel 和 Authelia 只有简短的说明,没有逐屏验证过,所以请对照提供商的最新文档核对每一项设置,并使用 Specivo 内置的连接测试。单点登录指南解释了每一项设置,另有 Keycloak、authentik、Microsoft Entra ID 和其他提供商的专门页面。

你能得到什么:

  • 同时接入多个提供商。每个提供商在登录页上有自己的按钮,有连接测试,客户端密钥加密存储。密钥也可以改为从 SPECIVO_SSO_SECRET_* 环境变量读取。
  • 首次登录自动建号。Specivo 可以在用户第一次登录时创建账户。已有账户的用户可以在 个人资料 → 已关联的账户 中关联,你也可以允许按已验证邮箱自动关联。这三项默认都是关闭的。
  • 用户组映射。身份提供商中的用户组可以映射到 Specivo 的用户组和管理员标志。每个提供商还可以要求用户必须属于特定用户组,并把登录限制在一组邮箱域名内。
  • 退出登录联动提供商。从 Specivo 退出登录时,可以同时在提供商处退出。开启后端通道登出(back-channel logout)后,在提供商处结束的会话也会在 Specivo 中结束。
  • 登录页选项。显示提供商按钮、隐藏密码表单、直接跳转到某一个提供商,或者只为管理员保留密码登录。
  • 审计事件覆盖登录、账户关联、用户组变更、退出登录和提供商变更。偏好设置 → 安全 下新增了会话列表,显示每个会话的登录方式。

在底层,Specivo 对每个提供商都使用带 PKCE(S256)、state 和 nonce 的授权码流程。服务账户(AI 智能体使用的那类账户)永远不能通过单点登录登录,API 密钥也不受影响。

本地账户照常可用。如果某项登录页设置把你自己锁在了门外,在登录 URL 后加 ?local=1,或者使用 SSO_FORCE_LOCAL_LOGIN 设置,就能重新进入。测试期间请保留一个有密码的管理员账户。

单点登录只支持 OpenID Connect。不支持 SAML,也没有 SCIM 或 LDAP 目录同步:用户在第一次登录时才会出现。指南中有一节讲如何在人员离职时移除访问权限。

停用用户

管理员现在可以在用户页面上停用和重新启用用户。停用会结束该用户的所有会话,并关闭其 API 密钥。重新启用不会自动重新打开这些密钥,由你决定恢复哪些。

退出登录、密码和 API 密钥

会话相关的几处行为有了变化,大多是改进,但如果你有脚本对接 Specivo,有几项需要留意。

  • 退出登录改为表单。从书签或链接打开 /logout/ 时,现在会先询问“退出登录?”。POST /api/v1/auth/logout/ 返回 200 并附带 end_session_url,而不再是 204。除非会话来自支持远端登出的提供商,否则该值为 null。
  • 退出登录真正退出。以前,服务端会话和访问令牌会一直有效到过期。现在两者同时结束。
  • 重置密码会结束该账户的所有会话,无论是通过邮件、由管理员操作还是在命令行完成。只通过单点登录登录的用户无法通过邮件重置密码。
  • 管理员 API 密钥的权限收窄。以下操作会被拒绝并返回 403:重置密码、解锁或重新启用用户、创建用户或管理员、为其他用户签发密钥、更改单点登录设置。创建服务账户仍然允许。原先用密钥执行上述操作的自动化脚本,必须改为以已登录的管理员身份运行。

代理之后的客户端地址

现在,只有当请求经由 TRUSTED_PROXIES 中列出的代理进入时,Specivo 才会从 X-Forwarded-For 读取客户端地址。TRUSTED_PROXY_HOPS 指定链路上串联了多少层代理,默认为 1。这也修复了内置 nginx 之后的所有客户端共用同一个限流桶的问题,审计日志中显示的也将是真实地址。内置 nginx 的访问日志不再记录查询字符串,因为其中可能包含单点登录流程中的 code 和 state。

安全修复

  • 只携带刷新 Cookie 的请求会跳过 CSRF 检查。
  • 令牌刷新可能与会话撤销产生竞争,导致会话被保留。
  • 退出登录后,服务端会话和访问令牌会一直有效到过期。见上文。

其他小改动

  • 从用户表中删除了未使用的 github_id 和 google_id 列。

升级时要做什么

先备份数据库和数据目录。升级会在启动时应用三个迁移(0041 到 0043)。保持原有行为无需任何配置,已登录的用户也会保持登录状态。

  • 内置部署栈新增了一个 proxy 网络。请使用新的 docker-compose.yml:nginx 在该网络上使用固定地址,这样 Specivo 就能只信任这一个地址。如果 docker compose up -d 因“Pool overlaps”而停止,在 .env 中把 SPECIVO_PROXY_SUBNET 和 SPECIVO_NGINX_IP 设为一个空闲的网段和地址。
  • 使用自己的 Compose 文件或 nginx 配置?用同样的方式添加该网络,或者把 TRUSTED_PROXIES 设为你的代理地址。TRUSTED_PROXIES 为空时,代理之后的所有人都共用一个地址。如果内置 nginx 前面还有另一层代理,设置 TRUSTED_PROXY_HOPS=2。自定义的 nginx/default.conf 必须保留 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;。
  • 如果要开启单点登录,先设置 APP_URL。重定向地址和登出地址都由它生成。
  • 轮换 SECRET_KEY 现在意味着还要重新输入 SSO 客户端密钥,因为这些密钥是用由它派生的密钥加密的。请把 .env.local 和数据库转储一起备份。
  • 期望退出登录返回 204 的脚本必须改为接受 200;使用管理员 API 密钥执行上述操作的自动化也必须调整。

完整细节见升级指南。

如何获取

运行 docker pull specivo/specivo:0.10.0 拉取 Docker 镜像。无法访问互联网的服务器可以使用 GitHub Release 中附带的离线包。安装和升级步骤见用户指南。