跳转至

OIDC / OAuth2.0 登录对接部署指南

本指南说明如何将 Shortener 对接任意标准 OIDC / OAuth2.0 身份提供方(IdP), 实现单点登录(SSO)。配套配置项见 配置指南 · OIDC 配置, 设计决策见 ADR 0001

概述

Shortener 支持两条并存的登录通道:

  1. OIDC 通道:对接外部 IdP(Keycloak、Authelia、Okta、Entra ID、Google 等),走标准授权码流。
  2. 密码通道:配置文件中的单管理员账号(Argon2id 哈希口令)。

两条通道均签发无状态 JWT(HS256),前端统一以 Bearer 令牌使用,互不影响。

OIDC 登录采用单身份 + 白名单模型:

  • 本服务没有账号系统 / 用户表,用户身份完全来自 IdP,不落库。
  • allow_emails / allow_subjects 白名单内的 IdP 用户可登录(任一命中即放行)。
  • 白名单留空时,放行任意已认证用户(仅建议临时测试使用)。

前置条件

  • 一个可用的 OIDC IdP,且你能在其上创建 OAuth2 / OIDC 客户端。
  • 已部署 Shortener 服务(服务端 v0.2.0+,包含 OIDC 与 JWT 支持)。
  • 用于签发 JWT 的 JWT_SECRET(所有实例必须一致)。

步骤一:准备 JWT 签名密钥

两条登录通道签发的 JWT 都用 JWT_SECRET 签名。请在所有实例使用同一个值,否则多实例间令牌互不相认。

export JWT_SECRET="$(openssl rand -base64 48)"

建议通过环境变量 / Secret 管理注入,不要硬编码进配置文件。

步骤二:在 IdP 侧创建客户端

以通用 OIDC IdP 为例:

  1. 新建一个 OAuth2 / OIDC 客户端(应用类型选「Web / 公开或机密均可」)。
  2. 授权方式选择 Authorization Code(授权码流)response_type=code
  3. 配置 Redirect URI / Callback URL,填写本服务的回调地址:
https://<你的域名>/api/account/oidc/callback
  1. 申请 Scope:openidprofileemail
  2. 若 IdP 要求客户端密钥(confidential client),记下 client_secret

如果使用 Keycloak:在 Realm 下创建 Client,Valid Redirect URIs 填上面的回调地址, Web Origins 填前端域名;在 Clients 的 Credentials 标签页获取 Secret。

步骤三:配置 Shortener

编辑配置文件(默认 config/config.toml),新增 [oidc] 段:

[oidc]
# IdP 的 issuer。discovery 文档位于 <issuer>/.well-known/openid-configuration
issuer = "https://keycloak.example.com/realms/shortener"

# 步骤二中创建的客户端 ID
client_id = "shortener-app"

# 客户端密钥。强烈建议改用环境变量 OIDC_CLIENT_SECRET 注入,勿写入文件
client_secret = ""

# 必须与 IdP 中登记的 Redirect URI 完全一致
redirect_uri = "https://shortener.example.com/api/account/oidc/callback"

# 白名单:email 或 sub 任一命中即放行
allow_emails = ["admin@example.com"]
allow_subjects = []

环境变量覆盖(敏感项优先):

export OIDC_CLIENT_SECRET="your-client-secret"   # 等价于 [oidc] client_secret

issuer 留空即表示不启用 OIDC 登录。

步骤四:准备密码通道(可选但推荐保留)

即使启用了 OIDC,也建议保留配置中的管理员账号,用于脚本 / 无 IdP 环境的运维登录。 口令必须以 Argon2id 哈希存储,生成方式:

# 服务端自带子命令(推荐)
shortener-server hash-password --password "your-secure-password"
# 或使用 CLI
shortener-cli hash-password --password "your-secure-password"
# 交互式(不在 shell 历史留痕)
shortener-server hash-password

将输出的整行($argon2id$...)填入配置:

[admin]
username = "admin"
password_hash = "$argon2id$v=19$m=19456,t=2,p=1$...."

步骤五:启动并验证

启动服务(确保已注入 JWT_SECRET):

JWT_SECRET="$JWT_SECRET" shortener-server

手动验证 OIDC 流程:

# 1) 触发登录,应 303 重定向到 IdP 授权页
curl -s -i "https://shortener.example.com/api/account/oidc/login" | grep -i "^location:"

# 2) 用浏览器打开上面的 location 完成 IdP 登录,
#    IdP 会回跳到 /api/account/oidc/callback?code=...&state=...
#    回调成功后会 302 到前端并附带 ?token=<jwt>

前端登录页已内置「使用 OIDC 登录」按钮,点击即发起上述流程; 回调后页面自动从 URL 提取 token 并写入 localStorage

白名单行为说明

场景 结果
用户 email 在 allow_emails 放行,签发 JWT
用户 sub 在 allow_subjects 放行,签发 JWT
均不在白名单 403 ForbiddenUser is not in the OIDC allowlist
allow_emailsallow_subjects 均为空 放行任意已认证用户(仅建议临时测试)

增删白名单用户需修改配置并重启服务

多实例 / 容器部署

  • JWT 为无状态 HS256,只要所有实例共享同一 JWT_SECRET,即可横向扩展、共享校验。
  • client_secretJWT_SECRET 建议通过容器 Secret / 环境变量注入,而非写进镜像或配置文件。
  • 配合反向代理(Nginx / Caddy)时,需将 https://<域名>/api/account/oidc/callback 正确转发到本服务。

排错

  • issuer 配置错误 / 网络不通:服务启动后首次访问 /api/account/oidc/login 会返回 500, 检查 issuer 是否可从本服务访问、.well-known/openid-configuration 是否可达。
  • 回调 404 / 不匹配:确认 IdP 登记的 Redirect URI 与配置 redirect_uri 完全一致(含协议、域名、路径)。
  • 登录后 403:说明用户不在白名单,检查 allow_emails / allow_subjects
  • JWT 校验失败(多实例):确认各实例 JWT_SECRET 一致。
  • 启动报 JWT_SECRET environment variable is required:未注入 JWT_SECRET,请设置后重试。

安全建议

  • 生产环境务必设置 allow_emails / allow_subjects,不要留空放行所有人。
  • client_secretJWT_SECRET 通过环境变量 / Secret 管理,避免入库与进版本控制。
  • 配置中的 admin.password_hashoidc.client_secret 均属于敏感信息,勿提交到代码仓库。