OIDC / OAuth2.0 登录对接部署指南¶
本指南说明如何将 Shortener 对接任意标准 OIDC / OAuth2.0 身份提供方(IdP), 实现单点登录(SSO)。配套配置项见 配置指南 · OIDC 配置, 设计决策见 ADR 0001。
概述¶
Shortener 支持两条并存的登录通道:
- OIDC 通道:对接外部 IdP(Keycloak、Authelia、Okta、Entra ID、Google 等),走标准授权码流。
- 密码通道:配置文件中的单管理员账号(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 签名。请在所有实例使用同一个值,否则多实例间令牌互不相认。
建议通过环境变量 / Secret 管理注入,不要硬编码进配置文件。
步骤二:在 IdP 侧创建客户端¶
以通用 OIDC IdP 为例:
- 新建一个 OAuth2 / OIDC 客户端(应用类型选「Web / 公开或机密均可」)。
- 授权方式选择 Authorization Code(授权码流),
response_type=code。 - 配置 Redirect URI / Callback URL,填写本服务的回调地址:
- 申请 Scope:
openid、profile、email。 - 若 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 = []
环境变量覆盖(敏感项优先):
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$...)填入配置:
步骤五:启动并验证¶
启动服务(确保已注入 JWT_SECRET):
手动验证 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 Forbidden:User is not in the OIDC allowlist |
allow_emails 与 allow_subjects 均为空 |
放行任意已认证用户(仅建议临时测试) |
增删白名单用户需修改配置并重启服务。
多实例 / 容器部署¶
- JWT 为无状态 HS256,只要所有实例共享同一
JWT_SECRET,即可横向扩展、共享校验。 client_secret与JWT_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_secret与JWT_SECRET通过环境变量 / Secret 管理,避免入库与进版本控制。- 配置中的
admin.password_hash与oidc.client_secret均属于敏感信息,勿提交到代码仓库。