再认证的每一次误配置都有两个安全的兜底:总闸关闭 = 全链路零行为(评估器第一票豁免,端点退回纯 RBAC),AuditOnly = 只记录不拦截。这让灰度可以做到”先看清影响面,再切强制”,回滚永远只有一步。
灰度顺序
- 开启总闸:Host「功能分发管理」把租户级 Feature
identity.stepup置为启用。此时若无策略行、无声明基线,系统仍零行为——总闸只是解锁强制能力。演示作用域部署会由种子为两个试点用途(API Key 与 HMAC 客户端生命周期命令)预置「仅观察」策略行,作为灰度起点;种子按自然键缺失才创建,灰度推进后的行不会被回拨。 - 观察期(AuditOnly):为目标用途建
auditOnly策略行(操作见快速接入)。放行照旧,但每次”本应再认证”的请求都写一条StepUpAuditOnlyObserved安全审计。到安全审计页按Module = Identity、资源类型StepUpPolicy、动作过滤,回答三个问题:日均命中量多少、集中在哪些人群/时段、前端旧版本覆盖率是否足够(旧客户端收到 403 会直接失败)。 - 切强制:命中量可控后把策略行切为
required。保存即时失效本地快照并广播全部实例,无需重启。 - 回滚:任何时刻把策略行切回
auditOnly或off,或直接关闭总闸。策略行是数据面,回滚不丢配置。
阈值调优指引
全部阈值在 StepUp 配置节(默认值与约束见契约参考),按观察期数据调整,不要照抄默认值:
| 症状 | 调整 | 权衡 |
|---|---|---|
| 用户反馈弹窗太频繁 | 调大 WindowDefaultSeconds 或把该用途策略行窗口从 300 调到 900 | 窗口越长,凭据被盗后的可用时间越长 |
| 验证码输错导致挑战频繁作废 | 调大 MaxAttemptsPerChallenge | 放宽暴力尝试空间,建议 ≤ 8 并配合限流 |
| 撤销后凭据”诈尸”担忧 | 无需调整——版本校验实时生效 | 版本键 TTL 只影响键存活,不影响撤销即时性 |
| OTP 邮件/短信成本高 | 调大 OtpResendAllowSeconds | 用户等待变长;TOTP 用户不受影响 |
| 限流 429 误伤共享出口 IP | 调高 RateLimiting:StepUp 对应档位 | 分区按租户+主体+端点,通常无需全局调 |
专项说明:窗口 vs 一次性。低频高危操作(支付、权限变更、MFA 变更)建议一次性票据(窗口 0)或短窗口;高频批量场景(清单内连续多条敏感操作)用 300–900 秒窗口。一次性票据被业务失败”烧掉”后需要重新验证,这是把重放窗口压到零的代价。
Runbook 要点
完整的灰度与运维手册已随试点切片交付:BitzOrcasVNext 仓库 docs/architecture/07-security/0707-step-up-runbook.md(Redis 前置、总闸开关步骤、观察期解读、验收度量五项的审计查询面、阈值调优、丢失设备线下核身兜底、回滚即关 Feature);机制层面的长期决策另见同仓 ADR 0706。日常处置要点以本节为准:
- 变更入口:策略行与控制面设置只在管理页操作;数据库直改策略表会绕过护栏校验与快照失效广播,属于禁止操作。
- 诊断命令:安全审计页查询
Module = Identity的 Security 记录;六类动作(挑战签发、验证成败、凭据拒绝、观察放行、策略变更)覆盖全部判定路径。 - 验证演练:停 Redis 演练 fail-closed(见下节);删版本键演练告警链路——两者都应在预发环境定期执行,而不是等生产首次故障。
常见故障 Q&A
Q1:开启总闸后,全部请求返回 403 Identity.StepUp.PolicyUnavailable?
这是 fail-closed 生效,不是误拦。根因几乎总是凭据或策略存储不可达:Redis 未部署、连接串错误、网络分区。修复存储后自动恢复。注意设计语义:无 Redis 的部署中 identity.stepup 会被锁死为关闭(放行 = 纯 RBAC),而”总闸开了但 Redis 中途失联”则按策略不可用拒绝——前者防误开启,后者防绕过,两个方向都是有意为之。
Q2:审计里出现 GrantInvalid 且 reason=versionKeyMissing,怎么处理?
版本键 TTL 按”窗口上限 + 挑战 TTL + 安全余量”动态计算,正常情况下任何存活凭据必有存活版本键。出现该告警说明版本键被外部删除(人工清理 Redis、驱逐策略误伤、跨环境共用实例),校验侧按 fail-closed 拒绝并写高危审计。处置:排查 Redis 清理任务与过期策略,把 {app}:{env}:v1:stepup: 前缀排除在通用清理之外;凭据本身无需处理,用户重新验证一次即可。
Q3:GrantPurposeMismatch 审计数上升说明什么?
凭据有效但用途不符。当前实现中一张凭据绑定单一用途,跨用途重放必然命中——偶发一两例通常是前端并发排队时序问题;持续上升是攻击信号:有人拿到了有效凭据并尝试在别的敏感端点上复用。处置:从审计取出主体与来源 IP,按入侵排查流程处理(该用户会话撤销、凭据因版本机制已自然失效),并核对前端版本是否存在未带用途隔离的旧逻辑。
Q4:管理页保存策略行报 PolicyConflict 但我看不出哪里冲突?
PolicyConflict 的 detail 是逐字段原因:「窗口秒数超过 Host 上限 900。」「用途不在 Host 允许的租户可配置范围内。」「平台强制用途不得关闭或降级为观察模式。」——按 detail 逐项修正即可。若报「同租户、同用途、同作用范围已存在策略行」,说明作用范围逻辑集合相同(规范化后恒等),应编辑既有行。版本冲突(PolicyVersionConflict)则刷新重试。
Q5:策略行改了,为什么部分实例还是旧行为?
写入会本地失效快照并经 LocalResourceSync 广播全实例;广播之外还有兜底 TTL(PolicySnapshotTtlSeconds,出厂 60 秒)。超过一分钟仍不一致,检查各实例到消息通道的订阅健康度。
相关主题
诊断命令速查
排障时的固定动作:先看总闸与用途命中,再看审计判定链,最后才怀疑存储。
# 1) 当前调用者会被要求二次验证的用途清单——空数组说明总闸关闭或无行无基线。curl -s -H "Authorization: Bearer $TOKEN" \ -X QUERY https://localhost:5001/api/identity/step-up/my-purposes
# 2) 某一用途的决策预览:isRequired=false 时调用不会被拦,别在存储侧找原因。curl -s -H "Authorization: Bearer $TOKEN" \ https://localhost:5001/api/identity/step-up/factors/identity.user.remove
# 3) 策略行与控制面现状:确认窗口/因子/范围与记忆是否一致(防止旁路改动)。curl -s -H "Authorization: Bearer $ADMIN_TOKEN" \ -X QUERY https://localhost:5001/api/identity/step-up/policies判定链上的六类安全审计动作(Module = Identity,安全审计页可查):
| 动作 | 含义 | 排障指向 |
|---|---|---|
StepUpChallengeIssued | 挑战签发成功 | 引导型 403 已被正确消费 |
StepUpVerifySucceeded | 验证成功并签发凭据 | 因子内核工作正常 |
StepUpVerifyFailed | 验证失败(含原因机器码) | 用户输错、因子不可用或频控 |
StepUpGrantRejected | 业务端点凭据被拒 | 撤销/过期/用途不符,按 reason 细分 |
StepUpAuditOnlyObserved | 观察模式放行 | 灰度命中量数据源 |
StepUpPolicyChanged | 策略行或控制面变更 | 变更审计与责任追溯 |
观察模式审计信封样例(其余字段遵循统一审计信封契约):
// reason 携带机器可读上下文;challengeId 一律只落 SHA-256 哈希。{ "category": "Security", "module": "Identity", "resourceType": "StepUpPolicy", "resourceId": "identity.user.remove", "action": "StepUpAuditOnlyObserved", "result": "Success", "level": "Security"}