## Summary Introduces the unified enterprise identity core (R1-A) with LEGACY, SHADOW, and ACTIVE rollout modes, the organization domain model, external identity V2 foundation, and OAuth login routing through the identity core. ## Changes - **Identity Core** (`skillhub-auth/federation/*`): ExternalIdentityLoginModule, IdentityLoginGuard, IdentityCorrelationPolicy, ProfileAuthorityResolver with LEGACY/SHADOW/ACTIVE mode switching - **Organization Foundation**: Organization, OrganizationDomain, OrganizationMembership, OrganizationRoleBinding domain model with domain-proof verification - **External Identity V2**: ExternalIdentity, PreProvisionedLoginSubject, LegacyIdentityBindingDualReader for dual-read compatibility - **OAuth Routing**: OAuthLoginFlowService routes GitHub/GitLab OAuth through the unified identity core, hardened return-to handling - **Migrations**: V60-V65 (organization foundation, audit log context, external identity V2, preprovisioned login subject, login correlation policy, legacy backfill) ## Review - ✅ 160 files, +10,311/-26 - ✅ Default LEGACY mode: zero behavior change - ✅ ACTIVE mode: GitHub & GitLab OAuth login chains verified end-to-end (local staging) - ✅ SQL migrations V60-V65 applied successfully on PostgreSQL 16 - ✅ Backend tests: 1,010 tests, 0 failures (Java 21) - ✅ CI: Server Unit Tests ✅ | Web Build ✅ | Docs Build ✅ | E2E ✅ | RISC-V ✅ | DCO ✅ - ✅ 2 minor fixes applied: OAuthIdentityCoreException explicit handling, public OAuth browser endpoint routing ## Rollout See `openspec/changes/enterprise-identity-platform/rollout-plan.md`. Made with [Proma](https://proma.cool) · [GitHub](https://github.com/proma-ai/Proma)
29 KiB
统一身份与企业身份平台分批合并与上线计划
文档状态
- 状态:人工评审稿
- 适用范围:
enterprise-identity-platformRelease 1 及其后续认证、目录同步批次 - 当前参考实现:
feature/enterprise-login-release - 当前参考验证:见 verification.md
- 本文不授权创建 PR、合并、部署、修改生产配置或导入真实企业数据
本文回答的不是“完整企业身份平台最终长什么样”,而是“如何把已经完成的第一版实现拆成可以逐次合并、逐次部署、逐次启用和逐次验证的发布单元”。稳定需求仍以 specs/ 为准,架构决策仍以 design.md 为准。
1. 为什么需要分批
当前参考分支同时包含统一身份核心、Organization 安全边界、Login Connection 控制面、动态 OIDC 数据面、管理页面、数据库迁移和身份测试实验室。它已经作为一个整体通过本地验证,但整体通过不等于适合一次性在生产启用。
企业身份有三类不同风险:
- 账号风险:同一个外部身份是否稳定关联到原来的 Platform Account,是否可能误建号、误合并或覆盖可信资料。
- 租户风险:Organization、Membership、角色、连接和会话是否严格隔离,停用后是否立即撤权。
- 协议风险:OIDC、SAML、CAS 等协议的回调、签名、重放、出站请求和供应商差异是否处理正确。
如果三类风险同时开放,出现问题时很难判断是账号关联、组织权限还是协议 Adapter 导致,也难以只关闭故障面。因此必须把合并、部署和启用拆开。
2. 三种动作必须区分
| 动作 | 含义 | 是否对用户立即可见 |
|---|---|---|
| 合并 | 代码进入 main |
不一定 |
| 部署 | 构建产物运行在环境中,Flyway 可能执行向前迁移 | 不一定 |
| 启用 | 通过模式、数据面开关和 Organization allowlist 让请求进入新路径 | 是 |
一批代码可以先合并并部署,但保持默认关闭。只有该批验证通过后才启用;启用失败时优先关闭运行时入口,不删除数据库记录,也不执行破坏性降级迁移。
3. 总体发布序列
R1-A 统一身份核心与兼容迁移
-> R1-A2 公共 Provider Adapter 扩展(飞书/钉钉等,按 Provider 分批)
-> R1-B Organization 与 Login Connection 控制面
-> R1-C 动态 OIDC 登录数据面
-> OIDC 小范围灰度并扩大
-> R2-SAML(独立)
-> R3-CAS(独立)
-> R4-LDAP 登录(只有产品决策明确后)
认证面稳定后,另起 Provisioning 发布序列:
P1-SCIM 用户生命周期
-> P2-Group/组织关系同步
-> P3-Namespace entitlement 投影
-> P4-厂商目录连接器(按需分别交付)
认证面回答“登录者是谁”;Provisioning 面回答“企业中有哪些人和群组”;Entitlement 面回答“这些人获得哪些 Namespace/Skill 权限”。三者可以复用 Organization 和 Platform Account,但不得共享连接类型、生命周期状态或失败处理逻辑。认证面内部还要区分 Provider 协议与登录上下文:公共飞书/钉钉登录只建立平台 session,企业飞书/钉钉登录必须在 Organization/LoginConnection 上下文中执行。
4. Release 1 的三个合并批次
4.1 R1-A:统一身份核心与兼容迁移
目标
先让现有公共 OAuth 使用统一的账号状态、身份关联和资料权威规则,不开放企业登录入口。该批次证明“统一身份核心不会破坏现有登录”。
包含
- 协议无关的 Adapter descriptor、
IdentityAssertion、typed subject 和标准失败分类。 - 统一身份决策:已有 binding、预供给 subject、verified email、JIT、账号与 Membership guard。
- External Identity V2 用于企业登录基础;公共 OAuth 先接入统一身份核心决策门禁,legacy binding 继续作为持久化权威。
- 现有公共 OAuth 到统一核心的 bridge。
- merged、system、disabled 等账号状态保护及 Profile Authority。
- 公共 OAuth 在 ACTIVE 模式下遇到统一核心 Denied/Conflict 时先于 legacy binding 写入 fail closed,并保留低基数观测指标。
- 为后续企业认证准备的最小 Organization/Connection schema 与内部模型,但不开放管理入口。
- 本批次所需测试、迁移护栏和运维说明。
数据库策略
V54–V60 是一条已经联合验证的 expand-only 迁移链:
| Migration | 主要内容 |
|---|---|
| V54 | Organization、Domain、Membership 和角色基础 |
| V55 | 企业审计上下文 |
| V56 | Login Connection、revision、External Identity V2 和 session origin 基础 |
| V57 | 加密 Secret version |
| V58 | 预供给 immutable subject |
| V59 | 登录关联策略 |
| V60 | 兼容性校验与后续 public OAuth V2 迁移预留,不作为 R1-A 启用门槛 |
虽然 R1-A 不开放 Organization 管理功能,但 V56 及后续企业认证表依赖这条 schema 链。建议在 R1-A 一次部署 V54–V60,保持全部新业务入口关闭。不要为了让 PR 看起来更小而重新编号、拆改或跨批次修改已经部署的 migration。公共 GitHub/GitLab 可以保留 V60 影子回填和一致性校验;R1-A 不切换公共 OAuth 的运行时写入权威,生产登录仍写入 legacy identity_binding。
部署与启用
- 合并并部署,保持
SKILLHUB_IDENTITY_CORE_MODE=LEGACY。 - 验证 local login、公共 OAuth、CLI Device Flow、API Token 和既有 Session。
- 切换为
SHADOW,新核心只做比较,旧 binding 继续决定结果;观察 mismatch、fallback 和异常。 - 在独立验收通过后切换为
ACTIVE。 - OIDC 控制面、OIDC 登录数据面和 Organization allowlist 继续保持关闭/为空。
SKILLHUB_IDENTITY_CORE_MODE 对 platform-scoped 公共 OAuth 是全局模式,不能按 Organization 灰度。因此从 SHADOW 切换到 ACTIVE 是本批次明确的独立发布决策,不能被后续 OIDC 灰度顺带触发。
进入下一批的门槛
- 空库 V1–V60 和现有 V53→V60 升级均成功。
LEGACY、SHADOW、ACTIVE下公共 OAuth 返回同一 Platform Account。- local login、CLI Device Flow 和 API Token 外部契约不变。
- ACTIVE 下统一身份核心 Denied/Conflict 能够在 active/pending legacy binding 写入前 fail closed;日志不包含 subject、email 或 token。
- 公共 Provider 登录不会创建 Organization Membership、企业 session origin 或 Namespace 权限。
- 经过约定观察窗口后没有未解释的 mismatch、登录失败率或重复账号。
回滚
将 SKILLHUB_IDENTITY_CORE_MODE 切回 LEGACY 并重新验证现有登录。V54–V60 数据保留;不删除新表或 V2 企业身份数据,不执行 down migration。
4.1.1 R1-A2:公共 Provider Adapter 扩展
目标
在统一身份核心稳定后,按 Provider 单独接入飞书、钉钉等公共登录入口。该批次只证明“这个外部账号可以登录 SkillHub 平台账号”,不建立企业成员身份。
包含
- Provider 专属 authorization、token、userinfo 或 equivalent API 客户端。
- 稳定 subject 选择、email verified 语义、avatar/displayName 资料权威映射。
/api/v1/auth/methodscatalog 展示、图标、登录按钮和 returnTo 保留。- 公共登录回调进入统一身份核心或当前 R1-A 兼容入口。
- Provider 专属错误映射、日志脱敏、远程 I/O 超时与响应大小限制。
不包含
- Organization Login Connection。
- 企业成员 JIT 或预供给。
- 通讯录、部门、群组或 SCIM 同步。
- Namespace 权限自动授予。
进入下一批的门槛
- 真实 Provider 或可重复参考服务完成登录、重复登录、禁用账号和错误回调验证。
- 该 Provider 在 LEGACY/SHADOW/ACTIVE 或当前等价模式下返回同一 Platform Account。
- 未配置 client id/secret 时 catalog 不展示按钮;placeholder 不展示。
- 账号禁用、merged/system 账号、未验证 email 和资料写入边界与 GitHub/GitLab 一致。
- 登录成功只建立平台 session,不产生 Organization Membership 或企业 session origin。
4.2 R1-B:Organization 与 Login Connection 控制面
目标
让管理员能够建立企业身份边界、成员关系和 OIDC 连接配置,但普通用户仍不能从匿名入口发起企业登录。该批次证明“企业配置和权限模型正确”,不同时承担真实登录风险。
包含
- Organization、Membership、Domain、角色和 authority version 生命周期。
- 平台创建 Organization 与租户内部管理权限分离。
- Organization、成员、域名、角色和审计 API。
- Login Connection、不可变 revision、测试后激活和 suspend/disable 生命周期。
- client secret envelope encryption、Secret version 和脱敏摘要。
- Organization 与 Login Connection 管理页面。
- 稳定分页、租户隔离、并发冲突和审计测试。
- 控制面所需的 OIDC 配置校验与连接探测,但不开放匿名 start/callback。
部署与启用
- 保持 R1-A 已验收的 identity core 模式。
- 注入 Secret keyring 和固定的 OIDC public base URI 等运行配置。
- 设置
SKILLHUB_ENTERPRISE_OIDC_ENABLED=true,只装配控制面。 - 明确保持
SKILLHUB_ENTERPRISE_OIDC_LOGIN_ENABLED=false。 - 保持
SKILLHUB_ENTERPRISE_ORGANIZATION_ALLOWLIST为空。 - 只创建测试 Organization,配置、测试并激活测试连接。
进入下一批的门槛
SUPER_ADMIN可以创建 Organization,但不能绕过目标 Organization Membership 管理租户数据。- Organization 角色满足最小权限;
IDENTITY_ADMIN不自动获得 Secret 管理权限。 - Secret 不出现在 API、UI、审计、异常、应用日志和容器启动日志。
- revision 未测试不能激活;暂停、禁用必须显式确认。
- Organization、Membership 和 Connection 状态变化正确递增 authority version,重复命令幂等。
- 匿名 discovery/start/callback 仍不可用,现有登录入口不受影响。
- 管理 API 的 OpenAPI 产物、Web typecheck/lint/test 和权限测试通过。
回滚
设置 SKILLHUB_ENTERPRISE_OIDC_ENABLED=false,关闭控制面组件。已创建的 Organization、Connection、revision、Secret 和审计记录保留,不影响现有公共登录。
4.3 R1-C:动态 OIDC 登录数据面
目标
在一个测试 Organization 内开放完整 OIDC authorization-code 登录,验证真实身份源、首次关联、重复登录、Session 和即时撤权,然后再逐个扩大 Organization allowlist。
包含
- 匿名 login discovery、enterprise start 和 callback。
- 固定 callback origin、opaque public handle 和安全
returnTo。 - 一次性 state、nonce、PKCE、浏览器绑定和 callback replay 防护。
- discovery/JWKS/token/UserInfo 出站访问安全。
- issuer、签名、算法、kid、audience/azp、时效和 nonce 校验。
- 首次登录关联、External Identity、企业 Session origin 和 authority version 重检。
- 匿名限流、代理信任、错误收敛、日志脱敏和安全审计。
oidc-fast、Keycloakoidc-real、真实浏览器和回滚演练。
部署与启用
- 部署后继续保持
SKILLHUB_ENTERPRISE_OIDC_LOGIN_ENABLED=false。 - 使用测试 Organization 完成控制面连接测试和激活。
- 确认 identity core 已经在 R1-A 独立验收为
ACTIVE。 - 只把测试 Organization 放入
SKILLHUB_ENTERPRISE_ORGANIZATION_ALLOWLIST。 - 设置
SKILLHUB_ENTERPRISE_OIDC_LOGIN_ENABLED=true。 - 完成首次登录、重复登录、退出、成员暂停、连接暂停和错误路径验收。
- 一个 Organization 一个 Organization 地扩大 allowlist;禁止直接使用全量或隐式通配。
进入扩大灰度的门槛
oidc-fastL2 和 Keycloakoidc-realL3 均基于该批 exact SHA 通过。- 真实浏览器完成 discovery → redirect → IdP → callback → session → logout。
- 首次登录和重复登录只保留一个 Platform Account、Membership 和 External Identity。
- 错误 state/nonce/signature/audience/issuer、callback replay 和跨 Organization 冲突全部 fail closed。
- Organization、Membership 或 Connection 暂停后,旧企业 Session 不等待 TTL 即被拒绝。
- 日志、报告和持久容器日志不含 token、code、state、nonce、Cookie、client secret 或测试密码。
- 本批的资源上限、清理脚本和回滚演练通过。
回滚
按以下顺序缩小故障面:
- 从 Organization allowlist 移除受影响组织。
- 设置
SKILLHUB_ENTERPRISE_OIDC_LOGIN_ENABLED=false。 - 如问题位于统一身份核心,再将
SKILLHUB_IDENTITY_CORE_MODE切回LEGACY。 - 必要时最后关闭
SKILLHUB_ENTERPRISE_OIDC_ENABLED控制面。
回滚不删除 Organization、Membership、Connection、External Identity、Session origin 或审计数据。
5. 测试必须跟随功能批次
不能先合功能、最后再补测试。每个批次至少同时携带以下验证资产:
| 层级 | R1-A | R1-A2 公共 Provider | R1-B | R1-C |
|---|---|---|---|---|
| L0 契约/架构 | Adapter 与身份决策边界 | Provider 与登录上下文分离 | 租户/RBAC/Secret 边界 | 协议和匿名入口边界 |
| L1 单元/持久化 | 关联、兼容、迁移、账号 guard | subject、verified email、错误映射、catalog | 生命周期、并发、分页、审计 | OIDC 校验、事务、限流、Session |
| L2 exact-SHA | 现有认证与数据库升级 | 配置开关、按钮展示、returnTo、无企业副作用 | 控制面 default-off | oidc-fast、开关和回滚 |
| L3 真实实现/浏览器 | 不要求 | 真实厂商测试账号或明确参考服务 | 只要求连接探测 | Keycloak 与真实 Windows 浏览器 |
Keycloak、CoreDNS、Caddy、mock provider 都是隔离的测试设施,不是生产依赖。测试 Profile 必须串行运行并在结束后清理容器、网络、volume、生成凭证和浏览器 Profile。公共 Provider 批次不能只靠 mocked catalog 按钮验收;按钮测试只证明展示和跳转,真实认证必须使用厂商测试账号、官方沙箱或写明边界的参考服务单独证明。
6. 当前参考分支如何重组
当前 feature/enterprise-login-release 保留为完整、已验证的参考实现,不对它做交互式 rebase 或历史重写。后续得到授权再按以下方式重组:
- 从当时最新
main创建 R1-A 工作分支。 - 按能力和文件依赖提取实现,把后期安全修复折叠回所属代码,而不是机械 cherry-pick 一段早期提交。
- 保证 R1-A 独立编译、迁移、测试和运行;完成审查、合并、部署与观察后结束该批。
- 从已经包含 R1-A 的最新
main创建 R1-B,重复完整验证。 - R1-B 合并并完成控制面验收后,再从最新
main创建 R1-C。 - 每批生成自己的 exact-SHA 验证记录;当前 verification.md 只能作为整体参考证据,不能替代重组后 SHA 的验证。
重组过程中可能需要少量代码调整来消除批次间的编译依赖。是否属于 R1-A、R1-B 或 R1-C,以运行时职责和最小可独立验证单元判断,不以原提交时间判断。
6.1 OpenSpec 归属
当前 enterprise-identity-platform change 保留为总体架构、完整行为边界和参考验证记录。真正开始重组时,R1-A、R1-B、R1-C 分别建立自己的 OpenSpec change,并分别维护:
- 本批 proposal、delta spec、design 和 tasks。
- 本批相对最新
main的明确范围与非目标。 - 本批 migration、配置、兼容和回滚边界。
- 本批 exact-SHA 验证记录。
不能让三个合并批次共同复用当前已经全部勾选完成的 tasks.md,否则后续无法判断某个 PR 到底完成了哪一批要求。子 change 只引用本总体设计,不复制整份架构说明;稳定行为在对应批次完成并验收后,再按 OpenSpec 流程归档到正式 specs。
6.2 当前参考工作区的本地审查包
当前 feature/enterprise-login-release 不是最终 PR 形态。人工审查时先按以下四组看,不把它们理解成必须一次合并的单个补丁:
A. OpenSpec 与上线边界
目的:确认统一身份、企业登录、公共 Provider 和组织同步的职责边界,避免把 SCIM、Namespace entitlement、飞书/钉钉和 V2 写入权威切换混入 R1-A。
openspec/changes/enterprise-identity-platform/proposal.mdopenspec/changes/enterprise-identity-platform/design.mdopenspec/changes/enterprise-identity-platform/rollout-plan.mdopenspec/changes/enterprise-identity-platform/tasks.mdopenspec/changes/enterprise-identity-platform/verification.mdopenspec/changes/enterprise-identity-platform/specs/**/spec.mddesign-qa.md
审查重点:R1-A 只要求 public OAuth 进入统一身份核心决策门禁;legacy identity_binding 仍是运行时写入和回滚权威。V60 影子回填是校验/回滚辅助,不等于切换写入权威。
B. 后端认证兼容与统一核心门禁
目的:在不改变现有 public OAuth 写入路径的前提下,让 GitHub/GitLab 等公共 OAuth 先经过统一身份核心决策;ACTIVE 模式下 Denied/Conflict 必须在 active/pending legacy binding 写入前 fail closed。
server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/LegacyPlatformIdentityCore.javaserver/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/LegacyPlatformIdentityDecision.javaserver/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/LegacyPlatformIdentityCoreBridge.javaserver/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuthLoginFlowService.javaserver/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuthLoginRedirectSupport.javaserver/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/oauth/*Test.javaserver/skillhub-app/src/main/java/com/iflytek/skillhub/service/AuthMethodCatalog.javaserver/skillhub-app/src/test/java/com/iflytek/skillhub/service/AuthMethodCatalogTest.java
审查重点:LEGACY/SHADOW 不改变旧登录结果;ACTIVE 只在统一核心明确拒绝或冲突时阻断。错误日志不得包含 subject、email、token 或 client secret。
C. 配置驱动登录页与回跳安全
目的:登录页由服务端 auth catalog 决定显示哪些入口;个人账号、企业账号和 public OAuth 可以同时存在,但没有配置的入口不展示。登录成功默认回首页,显式安全 returnTo 保留来源。
web/src/pages/login.tsxweb/src/pages/register.tsxweb/src/features/auth/auth-shell.tsxweb/src/features/auth/auth-entry-switch.tsxweb/src/features/auth/enterprise-login-discovery.tsxweb/src/features/auth/login-button.tsxweb/src/shared/lib/auth-route.tsweb/src/api/types.tsweb/src/app/layout.tsxweb/src/assets/login-network.webpweb/src/assets/login-network.webp.json- 对应
web/src/**/*.test.tsx? web/e2e/enterprise-identity-flow.spec.tsweb/src/i18n/locales/{zh,en,ru}.json
审查重点:前端不能硬编码 GitHub、飞书、钉钉等入口为“默认可用”;按钮只来自服务端 catalog。mocked catalog 只能证明展示和跳转,不能证明真实 Provider 登录。
D. Organization 管理入口的 UX 边界
目的:成员可以看到自己所属 Organization,但没有组织管理角色时不能进入管理详情;后端 403 仍是最终权限边界。
web/src/pages/dashboard/organizations.tsxweb/src/features/organization/organization-admin-shell.tsxweb/src/pages/dashboard/organization-admin-pages.test.tsx
审查重点:一个账号可以属于多个 Organization;列表页不是“当前企业唯一入口”。是否要改成单组织优先体验是产品交互问题,不应削弱多组织模型。
不进入第一批的内容
- 飞书、钉钉真实 Adapter 合并。
- SAML、CAS、LDAP 登录。
- SCIM、Directory、Group 或 Namespace entitlement 同步。
- public OAuth 运行时写入权威切到 External Identity V2。
- 生产环境导入真实企业组织或真实目录数据。
7. 合并与上线纪律
- 同一时间只推进一个功能批次,不预先堆积多个相互依赖的开放 PR。
- 每批必须基于最新
main,记录 base SHA、head SHA、镜像 digest、migration 版本和配置状态。 - PR 合并不等于允许部署;部署成功不等于允许启用。
- Flyway migration 一经共享环境执行即视为不可变,只能追加修复 migration。
- 未完成该批退出门槛时,不开始下一批。
- 生产启用先使用一次性测试 Organization 和测试身份,不直接导入真实企业目录。
- 任一关键指标异常时先关闭最窄入口,再判断是否需要应用版本回滚。
8. 后续认证协议的交付模板
OIDC 稳定后,SAML、CAS 和可能的 LDAP 登录分别建立独立 OpenSpec。每个协议批次只新增:
- 协议 Adapter 和版本化配置 schema。
- 控制面校验、连接探测和 runtime snapshot materializer。
- 协议特有的安全校验与错误映射。
- 一个成熟的开源参考服务或可重复的测试实现。
- 真实协议流程、浏览器验收、故障路径和回滚说明。
这些批次复用统一身份核心、External Identity、Organization、Membership、Profile Authority 和 Session guard。Adapter 仍然不得决定建号、账号合并、成员授权或 Namespace 权限。
LDAP 可能同时被用作“用户输入企业密码直接登录”和“后台读取企业目录”。这两种能力必须使用不同连接类型和安全边界:前者属于 authentication,后者属于 provisioning。没有明确 TLS、稳定 object id、密码不落盘和连接隔离方案前,不把 LDAP 标记为已支持的登录协议。
9. 组织同步的独立发布序列
组织同步不进入 Release 1。后续建议至少拆为三个批次:
P1:SCIM 用户生命周期
- 建立 Directory Connection 与 source-owned 属性边界。
- 支持 User 创建、更新、停用和幂等重放。
- 维护外部目录对象 ID,不用 email 作为稳定主键。
- 不自动授予 Namespace 权限。
P2:Group 与组织关系同步
- 同步 Group、成员关系和删除/停用语义。
- 定义全量调和、增量事件、游标、水位和冲突处理。
- 手工 Membership 与目录 Membership 不互相覆盖。
P3:Namespace entitlement 投影
- 单独配置 Group/Organization 到 Namespace role 的映射。
- 使用 source-aware grant,删除目录来源时不误删手工授权。
- 提供 dry-run、差异预览、审计和可恢复撤权。
厂商连接器按需建立在 P1/P2 之后。钉钉、飞书、企业微信或 LDAP Directory 的数据模型差异由各自 Adapter 吸收,不能把厂商字段直接扩散到 Platform Account、Organization 或 Namespace 核心模型。
10. 人工评审需要确认的决定
在开始重组代码前,需要人工明确接受或修改以下决定:
- 是否接受 R1-A 先部署完整 V54–V60 expand-only schema,但暂不开放 Organization/OIDC 功能。
- 是否接受公共 OAuth 按
LEGACY → SHADOW → ACTIVE独立完成统一身份核心切换。 - 是否接受 R1-B 允许管理员配置并测试 OIDC Connection,但匿名企业登录保持关闭。
- R1-C 首个灰度 Organization、负责人、观察窗口和停止条件是什么。
- OIDC 之后优先做 SAML、CAS 还是 LDAP 登录;每种协议仍保持独立批次。
- 是否确认 SCIM/Directory、Group 同步和 Namespace entitlement 分别立项,不与认证协议混合。
以上六项确认前,只保留当前参考分支和文档,不创建分批 PR。
11. R1-A 本地提取作业单
本节是从当前参考工作区重组第一批 PR 时的操作边界。它不是新的产品范围;只把本文前面已经定义的 R1-A 落成可检查的文件与验证清单。
11.1 第一批只解决什么
R1-A 的可交付结果是:
现有 public OAuth/GitHub/GitLab
-> 提取已验证 facts
-> 经过统一身份核心决策门禁
-> LEGACY/SHADOW 不改变旧结果
-> ACTIVE 下 Denied/Conflict 在 legacy binding 写入前 fail closed
-> legacy identity_binding 仍是 public OAuth 运行时写入和回滚权威
登录页改版、Organization 管理 UI、动态 OIDC 数据面和飞书/钉钉 Adapter 都不是 R1-A 的必要条件。若为了编译保留少量 schema/config/model,也必须保持入口默认关闭,且 PR 描述中明确“为后续批次铺 schema,不开放用户入口”。
11.2 建议进入 R1-A 的文件组
后端认证兼容核心:
server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/LegacyPlatformIdentityCore.javaserver/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/LegacyPlatformIdentityDecision.javaserver/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/LegacyPlatformIdentityCoreBridge.javaserver/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuthLoginFlowService.javaserver/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/oauth/LegacyPlatformIdentityCoreBridgeTest.javaserver/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/oauth/OAuthLoginFlowServiceTest.java
如果 R1-A 同时承载“登录成功默认回首页、returnTo 安全收敛”这个兼容修复,则可以加入:
server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuthLoginRedirectSupport.javaserver/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginSuccessHandler.javaserver/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/oauth/OAuthLoginRedirectSupportTest.javaserver/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginHandlersTest.javaweb/src/shared/lib/auth-route.tsweb/src/shared/lib/auth-route.test.ts- 导航栏登录 returnTo 保留所需的最小
web/src/app/layout.tsx改动
若加入 returnTo 修复,PR 标题或正文必须单独列为“登录回跳安全兼容修复”,避免 reviewer 误以为它是企业登录 UI 的一部分。
11.3 不应进入 R1-A 的文件组
以下内容应留到 R1-B/R1-C 或 UI 批次,除非人工明确改变拆批策略:
web/src/pages/login.tsx和web/src/pages/register.tsx的分屏 UI 大改。web/src/features/auth/auth-shell.tsx、auth-entry-switch.tsx、login-network.webp。server/skillhub-app/src/main/java/com/iflytek/skillhub/service/AuthMethodCatalog.java中声明ENTERPRISE_DISCOVERY的用户入口逻辑。server/skillhub-app/src/main/java/com/iflytek/skillhub/service/EnterpriseLoginAppService.java的 discovery fallback UI 逻辑。- Organization 列表/管理入口 UX 调整。
- 飞书、钉钉、SAML、CAS、LDAP、SCIM、Directory、Group、Namespace entitlement。
- public OAuth runtime write authority 切到 External Identity V2。
这些文件可以在参考分支保留,供人工体验完整效果;但第一批 PR 不应夹带,否则 R1-A 会变成“统一身份核心 + 新登录页 + 企业发现入口”混合风险。
11.4 R1-A 验证命令
提取到新的 R1-A 分支后至少运行:
openspec validate <r1-a-change-id> --strict
git diff --check
cd server
MAVEN_OPTS='-Xmx768m -XX:ActiveProcessorCount=2' ./mvnw -pl skillhub-app -am \
-Dtest=OAuthLoginFlowServiceTest,LegacyPlatformIdentityCoreBridgeTest,OAuth2LoginHandlersTest,OAuthLoginRedirectSupportTest \
-Dsurefire.failIfNoSpecifiedTests=false test
MAVEN_OPTS='-Xmx768m -XX:ActiveProcessorCount=2' ./mvnw -pl skillhub-app -am -DskipTests package
cd ../web
pnpm exec vitest run src/shared/lib/auth-route.test.ts --maxWorkers=1
pnpm run typecheck
pnpm run lint
如果 R1-A 没有带任何 Web 改动,则 Web 命令可以退化为 OpenAPI freshness 和现有认证入口冒烟;但 PR 描述必须说明为什么没有登录页/UI 变更。
11.5 R1-A 人工验收
R1-A 人工验收只看现有登录是否被统一核心门禁破坏:
LEGACY:local login、GitHub/GitLab public OAuth、CLI Device Flow、API Token 行为不变。SHADOW:public OAuth 登录结果不变;统一核心异常不影响旧登录结果;日志不含 subject、email、token 或 secret。ACTIVE:正常 public OAuth 仍登录到同一 Platform Account;统一核心 Denied/Conflict 时 fail closed,且不创建 active 或 pending legacy binding。- public OAuth 登录不会创建 Organization Membership、企业 session origin 或 Namespace 权限。
- 如果包含 returnTo 修复:无 returnTo 登录回
/;安全应用内 returnTo 保留 query/hash;外部 URL、协议相对 URL、反斜杠和控制字符回/。
只有以上通过后,才进入 R1-B 控制面。不要因为参考分支里的完整登录页已经能跑,就跳过 R1-A 的独立验收。