Skip to content

模拟项目 A:企业 SSO 接入与会话治理 ​

状态:模拟设计,未在当前工作区实现。 目标角色是 1–3 年 Java 后端工程师,负责应用接入和会话治理模块;团队使用成熟 IdP,不把配置 IdP 写成独立实现 OAuth/OIDC 协议。

一、业务问题与范围 ​

模拟企业有知识库、工单两个应用。原先各自登录,账号禁用和权限更新不能一致生效。目标是统一身份入口,同时保留各应用的业务授权和本地会话。

第一期:两个应用、两个测试租户、普通成员与管理员角色;OIDC 授权码流程、PKCE、受保护 API、多租户隔离与退出处理。后续才考虑外部企业 IdP 联邦、SAML 和 SCIM,不写成一期已支持能力。

候选技术栈:Java + Spring Boot/Security、Keycloak、PostgreSQL、Redis、JUnit/Testcontainers。实施前锁定兼容版本、镜像摘要和配置导出,本文不提供未经执行验证的安装命令。

二、架构与登录时序 ​

text
浏览器 ── 应用 A/B 的 BFF(独立本地会话)
               │             │
               ├── OIDC ── IdP(登录与令牌签发)
               ├── Redis(应用会话、短期事务状态)
               └── API(令牌验证 + 租户/资源授权)── PostgreSQL
  1. BFF 为本次登录保存一次性的 state、nonce、PKCE verifier 和允许返回的位置。
  2. 浏览器跳转 IdP;IdP 完成认证后只将授权码送回已注册回调。
  3. BFF 核对登录事务,使用 verifier 换取令牌,验证 ID Token 后建立自己的会话。
  4. 浏览器只持有受保护的会话 Cookie,访问 API 所需令牌保存在服务端。
  5. 访问应用 B 时,B 仍走自己的 OIDC 流程;IdP 已有登录会话,因此可能不再要求输入密码。不是 A 把自己的 Cookie 发给 B。

OIDC 是身份层,OAuth 处理授权,JWT 是一种令牌格式。ID Token 的受众是客户端,不能直接当任意业务 API 的通行证。OIDC Core。

本方案使用 PKCE S256、精确回调匹配,并避免密码模式;state/nonce 与事务绑定,不在日志打印码或令牌。PKCE 不能替代所有 CSRF 和会话保护。RFC 9700。Cookie 设置需结合实际域名、HTTPS 与回调方式验证;HttpOnly 不等于没有 CSRF。

三、数据与接口契约 ​

以下是应用自定义模型,IdP 自己管理密码、签发密钥与协议令牌。

表/记录关键字段与约束
identity_linkinternal_user_id、issuer、subject;UNIQUE(issuer, subject),不能按邮箱静默合并账号
tenant_membershiptenant_id、user_id、role、status、permission_version;联合唯一约束
app_sessionopaque session_id 的安全引用、issuer、sid、user_id、tenant_id、expires_at、status
login_transaction随机事务标识、state、nonce、verifier、过期时间;一次性消费,短期保存
audit_eventevent_id、request_id、user_id、tenant_id、app_id、decision、reason、timestamp;无明文令牌

建议应用接口:GET /login 发起登录;框架管理回调;GET /session 查询脱敏会话;POST /logout 退出本应用;POST /internal/backchannel-logout 接收经过验证的退出通知;GET /documents/{id} 同时验证租户与资源权限。接口名称是拟定契约,不声称已上线。

BFF 的会话和服务间 Access Token 是不同对象。Resource Server 在业务授权前验证可信 issuer、签名算法、audience 与有效期;不能只做 Base64 解码或只验证签名。Java 分工参考 Spring Security OAuth2。

四、四个值得深挖的项目点 ​

A1:跨应用 SSO 接入 ​

为两个应用分别配置回调和会话,复用 IdP 的认证状态;验证一次性登录事务及授权码兑换绑定。证明材料:两应用登录录屏、错误回调与重复消费测试。不能把“两个页面都能打开”当作 SSO 验证。

A2:账号映射与租户隔离 ​

只信任配置允许的 issuer;以 issuer+subject 识别外部身份。邮箱变更不产生新账号,两个 issuer 同邮箱不自动合并。租户切换由可信 membership 决定,不直接接受前端 tenant_id。证明材料:两应用登录录屏、账号碰撞测试、跨租户拒绝轨迹。

A3:多实例会话与刷新并发 ​

应用共享会话存储,不把所有登录状态放单机内存。对同一会话的刷新使用单飞协调或版本化更新,避免并发请求各自使用旧 Refresh Token;失败不能原样无限重试。若引入刷新轮换,应核对 IdP 的配置与重用检测规则,不能仅凭配置名称保证效果。模拟断点:换票成功但保存新凭据前进程退出,明确何时需要重新登录。

A4:退出与撤销的可见性 ​

本地退出、IdP 退出、所有应用会话清除、Access Token 失效是四件事。处理有效 Logout Token 时按 issuer 和 sid/sub 定位会话,重复通知应幂等;缺少通知不能假定所有应用已退出。Back-Channel Logout。

自包含 JWT 即使 IdP 会话被删,也可能在有效期内继续通过离线验签。本方案为高风险业务增加服务端会话/权限状态检查;普通读取允许怎样的滞后必须明确。不要宣称“删除 Redis 就让所有 JWT 全球立即失效”。

配套设计:权限缓存与密钥轮换 ​

权限缓存键包含租户、用户、资源/动作和策略版本,不能只按用户缓存。更新时发失效事件并设置最大存活期;事件丢失时用版本或到期重新检查,不能跨租户回退到默认权限。JWKS 正常轮换时保留验证未过期旧令牌所需公钥;密钥泄露则走紧急撤销策略,不按普通重叠窗口处理。

五、异常与验收设计 ​

编号输入/故障必须观察的结果
S01已登录 A 再访问 BB 有独立本地会话,IdP 可复用认证;不是复制 A Cookie
S02回调 state 不匹配/重复使用事务拒绝,不创建本地会话
S03授权码配错 verifier换票失败,没有令牌下发
S04issuer/audience 错误或过期令牌API 拒绝;日志无令牌正文
S05T1 用户读 T2 文档不返回文档正文、摘要或敏感存在性信息
S06两个请求并发刷新无无限重试,最终凭据版本一致或明确要求重新登录
S07有效退出通知重复到达清除目标会话,重复操作幂等
S08退出通知丢失能测出剩余窗口,按约定兜底,不报告全局已完成
S09权限取消但失效事件丢失在定义的最大窗口后拒绝,高风险写入即时复查
S10IdP/JWKS 暂不可用未知 key 不盲信;已缓存 key 只按既定策略验证未过期令牌

所有 S01–S10 都是待执行用例。压测另行固定硬件、实例数、请求分布、缓存命中率与是否包含 IdP 网络耗时,记录 P50/P95/P99、错误率和总请求数。登录链路与业务鉴权链路分开测,不能互相替代。

六、可转成简历的表达 ​

完成并留证后可写:“负责知识库与工单应用的 OIDC SSO 接入,使用授权码与 PKCE 完成服务端换票和独立会话建立,并通过 issuer+subject 映射及租户授权校验处理账号冲突与越权风险。”

当前只能写“设计/研究上述方案”。性能收益、接入应用数和上线规模均须替换成真实值。更多表述见项目点清单。