IdP 宕机不直接踢掉已登录用户,前提是已签发的令牌和本地会话在 IdP 不可达时仍有效。做法分三层:令牌短生命周期但本地会话长驻、SP 缓存公钥与会话状态做离线续期、IdP 本身做集群与多活。这样 IdP 短暂故障时,已登录用户照常办事,只有”新登录”和”会话过期重登”两类人受影响。下文给出具体机制。
一、先分清谁会受影响
Mattermost 的灾难恢复文档说得很直白:SSO 提供商故障时是”部分中断”。已持有有效会话令牌的用户(默认 30 天)能继续用,不受影响;两类人会被挡在门外——会话令牌在故障期间过期的用户,以及要在新设备首次登录的用户。
这告诉我们一个设计基准:把”已登录用户不中断”作为第一目标,把”新登录”作为可接受降级项。
| 用户状态 | IdP 宕机时 | 应对 |
|---|---|---|
| 已登录、会话有效 | 不受影响 | 本地会话续期 |
| 会话过期需重登 | 无法登录 | 离线宽限 / 降级登录 |
| 新设备首次登录 | 无法登录 | 紧急账号 / 备用 IdP |
二、核心机制:服务端会话缓存 + 离线宽限
SSO 可靠性设计推荐”缓存会话 + 渐进限制”模式:签发 15 分钟级 Access Token,服务端以 Redis 存会话记录并设”离线宽限”TTL(常见 1–72 小时)。IdP 不可达时,允许基于服务端状态续期,但收紧权限——只读、禁止提权、敏感操作强制重认证。
- OIDC 登录成功后,把会话句柄写入 Redis:
session:{handle}带用户与权限,TTL 1 小时; - 每个请求经网关校验 JWT,同时查 Redis 会话是否存在;
- 探测到 IdP 不可达时,若会话年龄未超离线宽限,放行但打
limited标记; - 敏感写操作(支付、改权限)要求重新跳转 IdP,IdP 不通则拒绝。
// 离线宽限中间件(示意)
func SessionGuard(rdb *redis.Client, idpDown func bool) gin.HandlerFunc {
return func(c *gin.Context) {
handle, _ := c.Cookie("sh")
sess, err := rdb.Get(c, "session:"+handle).Result
if err != nil {
c.Redirect(302, "/login")
c.Abort
return
}
if idpDown {
if sessionAge(sess) > OFFLINE_GRACE {
c.Redirect(302, "/login")
c.Abort
return
}
c.Set("limited", true) // 降级为只读
}
c.Next
}
}
三、IdP 自身高可用
单活的 IdP 是单点故障,必须集群化。CAS 的高可用指南给出两种模式:主动—被动(一个节点服务,故障切换重置 SSO 会话)与主动—主动(多节点同时服务,靠分布式票据注册表共享票据状态)。主动—主动下某节点下线升级,其余节点仍可响应,票据通过内存复制在节点间同步。
缓解 IdP 宕机的通用手段包括:负载均衡 + 多活、数据库与缓存冗余、会话状态复制、健康检查自动故障转移;SaaS IdP 还可配高可用 LDAP 等冗余数据源。
| 容灾层 | 手段 | 保住的对象 |
|---|---|---|
| 令牌 / 会话 | 本地长会话 + 公钥缓存 | 已登录用户 |
| SP 状态 | Redis 会话 + 离线宽限 | 故障期续期 |
| IdP 架构 | 多活集群 + 票据复制 | 新登录能力 |
| 紧急通道 | 备用登录 / 破窗账号 | 极端场景兜底 |
四、破窗与降级登录
IdP 长时间宕机时,需要”破窗”管理流:可审计、双人审批、用硬件密钥保护。临时把账号从 SSO 切到邮箱密码,故障结束再切回,保持一致性。降级策略只用于非关键业务,且要明确告知用户安全风险。
原则要”失败保守”而非”失败开放”:IdP 被攻陷时,降级应默认更严(逐步认证、降权),而非自动放行全部权限。定期演练故障切换,让离线宽限与破窗流是跑过的,而不是纸面方案。
常见问题(FAQ)
Q1:IdP 挂了已登录用户会被踢吗?
不会。本地会话与缓存令牌有效,仅新登录 / 过期重登受限。
Q2:离线宽限设多久合适?
按风险 1–72 小时不等;期内收紧为只读更安全。
Q3:紧急登录怎么不破坏安全?
用双人审批破窗流,临时邮箱密码,恢复后切回 SSO。