Skip to content

feat(sso): Add PKCE support, separate OIDC sub and username#2793

Open
Youwenqwq wants to merge 5 commits into
OpenListTeam:mainfrom
Youwenqwq:main
Open

feat(sso): Add PKCE support, separate OIDC sub and username#2793
Youwenqwq wants to merge 5 commits into
OpenListTeam:mainfrom
Youwenqwq:main

Conversation

@Youwenqwq

@Youwenqwq Youwenqwq commented Jul 15, 2026

Copy link
Copy Markdown

Summary / 摘要

为单点登录功能添加了可配置的 subject key,并增加了 PKCE 验证方式和 nonce 以在一定程度上增加安全性。

为什么要修改

在当前版本(v4.2.3, a92d59d)的 OpenList 中,单点登录功能若选择 OIDC 方式,将只有 “OIDC 用户名键” 可设定;在开启 SSO 自动注册后,该用户名键将作为 OpenList 中的 User.SsoID 用于绑定唯一身份。这存在一个问题: 通常用户可在 IdP 侧修改用户名,一旦用户名发生变更,将产生一个新的 SsoID,导致 OpenList 侧重复创建并绑定一个新的用户。

注意到原代码中,选择 Casdoor 作为提供商时,可以通过 subject key 来辨别 IdP 侧唯一用户;事实上大多数 IdP 都会向应用提供一个稳定的身份 ID,因此应作为通用 OIDC 选项进行配置。

另外,当前版本中,前端切换任意 SSO 登录平台似乎都只会显示通用的单点登录配置项,这个 PR 没有改变这个行为。

用户可感知的变化

  • 单点登录设置中,增加 OIDC subject key 填写项,PKCE 和 nonce 开关。若使用 PKCE,无需配置 client secret。
  • 删除了单点登录设置中没有实际作用的 “组织名称”,“SSO 应用名称” 和 “SSO JWT 公钥” (前两者代码上根本不传入,后者已由 Discovery 自动获取,无需手动配置)

重要实现变化

由 AI 总结,人工审核

  • 使用已验证的 ID Token 对象读取 claims,不再手动解码 JWT payload。

  • 将用户身份与显示用户名分离:

    • User.SsoID 来自 subject claim。
    • User.Username 来自 username claim。
  • PKCE verifier 和 nonce 存储在一次性 state 中,并在回调时消费。

  • state 同时绑定客户端 IP 和回调 method,过期 state 在 Pop 时也会被拒绝。

  • OIDC V1 回调和其他 SSO 回调共用安全的 HTML/重定向响应逻辑。

配置和兼容性

由 AI 总结,人工审核修改

新增配置:

sso_oidc_subject_key
sso_oidc_pkce_enabled
sso_oidc_nonce_enabled

默认值:

subject_key = ""
pkce_enabled = false
nonce_enabled = false

subject_key 留空时继续使用 OIDC Username Key 作为 SSO ID,因此现有部署默认保持原有身份映射。

将其改为 sub 后,已有用户的旧 SsoID 不会自动迁移,首次登录可能被识别为新用户;上线前需要评估现有用户映射。

没有数据库 schema 或唯一索引变更,也没有 SSO API 路由变更。被删除的遗留设置不会再被初始化或读取;数据库中已有
的旧设置记录不会主动删除,但没有运行时作用。

非兼容模式现在要求弹窗 opener 与 OpenList API URL 同源。依赖跨域 opener 的特殊部署需要调整来源配置或使用兼容模式。state 仍是进程内存储,多实例部署的回调路由限制与原实现一致。

  • This PR has breaking changes.
    / 此 PR 包含破坏性变更。
  • This PR changes public API, config, storage format, or migration behavior.
    / 此 PR 修改了公开 API、配置、存储格式或迁移行为。
  • This PR requires corresponding changes in related repositories.
    / 此 PR 需要关联仓库同步修改。

Related repository/ 关联仓库:

  • OpenList-Docs: 理论上应该修改文档描述,但考虑到单点登录这一块的 “SSO 登录平台” 选择功能在我目前所使用的 “v4.2.3” 暂时失效,目前已有文档可能需要一定修缮,故暂时不变更文档。

Testing / 测试

  • go test ./...
  • Manual test / 手动测试: 利用我已有的 Zitadel 实例进行单点登录,sub 和 preferred_username 正常读取,PKCE 正常授权,身份正常创建;Zitadel 侧修改用户名不再导致 OpenList 新建账户。

Checklist / 检查清单

  • I have read CONTRIBUTING.
    / 我已阅读 CONTRIBUTING
  • I confirm this contribution follows the repository license, contribution policy, and code of conduct.
    / 我确认此贡献符合仓库许可证、贡献规范和行为准则。
  • I have formatted the changed code with gofmt, go fmt, or prettier where applicable.
    / 我已按适用情况使用 gofmtgo fmtprettier 格式化变更代码。
  • I have requested review from relevant maintainers or code owners where applicable.
    / 我已在适用情况下请求相关维护者或代码所有者审查。

AI Disclosure / AI 使用声明

  • This PR includes AI-assisted content.
    / 此 PR 包含 AI 辅助内容。

Tools used / 使用工具:

  • ChatGPT
  • Codex
  • GitHub Copilot
  • Claude
  • Gemini
  • Other (please specify) / 其他(请注明):

Usage scope / 使用范围:

  • Code generation / 代码生成

  • Refactoring / 重构

  • Documentation / 文档

  • Tests / 测试

  • Translation / 翻译

  • Review assistance / 审查辅助

  • I have reviewed and validated all AI-assisted content included in this PR.
    / 我已审核并验证此 PR 中的所有 AI 辅助内容。

  • I have ensured that all AI-assisted commits include Co-Authored-By attribution.
    / 我已确保所有 AI 辅助提交都包含 Co-Authored-By 归属信息。

  • I can reproduce all AI-assisted content included in this PR without any AI tools.
    / 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。

Youwenqwq and others added 4 commits July 15, 2026 18:11
- Consume OIDC state once and bind it to the client, method, and IP.
- Encode popup and redirect values and restrict postMessage to the API origin.
- Reject expired entries when atomically popping keyed cache values.

Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
- Add an optional OIDC subject claim setting with legacy identity fallback.
- Read claims from the verified ID token and query UserInfo for missing values.
- Verify the UserInfo subject and use the username claim for auto-registration.

Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
- Add backward-compatible settings for S256 PKCE and OIDC nonce validation.
- Store verifiers and nonces in the one-time login state.
- Submit PKCE verifiers during token exchange and reject mismatched ID token nonces.

Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
- Remove unused organization, application, and JWT public key entries from active SSO settings.
- Drop the corresponding unreferenced configuration constants.
@xrgzs xrgzs added enhancement Module: User User and authentication related issue/PR Module: Server API and protocol changes labels Jul 15, 2026
@jyxjjj

jyxjjj commented Jul 21, 2026

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a37f4d33ab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +78 to +79
if entry.Expired() {
return *new(T), false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve recursive cache invalidation on expired parents

Changing Pop to return false for expired entries changes the generic remove-and-return semantics used by Cache.deleteDirectoryTree: if a parent directory cache entry has expired but a child directory cache was refreshed later or has a longer custom TTL, invalidating/deleting the parent stops here and leaves the child cache/link entries usable until their own TTL. Keep Pop as a raw remove or add a separate unexpired-pop helper for SSO state so tree invalidation can still walk expired parents.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Module: Server API and protocol changes Module: User User and authentication related issue/PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants