Skip to content
bitzorcas
中EN

Concept

敏感操作再认证(Step-Up)

登录 MFA 回答不了「还是你本人吗」。Step-Up 再认证为每个敏感操作单独校验身份:全局中间件唯一强制点、三层策略模型、六种因子复用既有验证内核。

Last updated

用户上午九点用密码加 TOTP 登录,下午三点手机被同事借用时点了一下「移除成员」。这次点击发生时,登录会话仍然有效——登录时的多因素认证证明的是「登录的那个人是谁」,回答不了「此刻操作的还是你本人吗」。会话有效期越长、设备共享越多,这道缝隙就越大。

敏感操作再认证(Step-Up re-authentication)把身份校验从「每个会话一次」细化为「每个敏感操作一次」:业务端点声明自己需要再认证,全局中间件在请求到达业务代码之前校验一张短时效的「再认证凭据」,凭据缺失就返回结构化的 403,由前端统一拉起验证对话框,验证通过后携带凭据重放请求。

与登录 MFA 的区别

两者共用因子内核(TOTP、OTP、恢复码),但在认证保证语义上是两件事。NIST SP 800-63B(SP 800-63B)将认证保证分级为 AAL1–AAL3(§4.3 “Authentication Assurance Levels”),会话管理与再认证时机的权衡交由部署方按风险决定——标准并未规定「敏感操作再认证不得超过 15 分钟」之类的条款,本文中出现的 300 秒 / 900 秒均为本平台出厂建议值,应按观察期数据调整。

维度登录 MFA(多因素认证)Step-Up 再认证
回答的问题登录的人是谁此刻操作的还是本人吗
校验时机密码通过后、令牌签发前令牌有效期内、敏感操作执行前
校验对象五分钟登录挑战 token每个操作单独签发的再认证凭据
校验主体LoginFlow / MFA 端点全局 StepUpEnforcementMiddleware
覆盖范围一次登录冻结目录内的任意权限码用途
失败语义拿不到访问令牌结构化 403,会话本身不受影响

两者还有一条协同线:MFA 变更本身是敏感操作。禁用 MFA、删除 FIDO2 凭据、撤销受信设备等端点声明了再认证基线——攻击者就算偷到有效会话,也不能静默拆掉保护;反过来,MFA 变更成功会发布 UserMfaChangedIntegrationEvent,撤销该主体全部在途再认证凭据。

业界对标

系统做法与本平台的异同
GitHub sudo mode敏感操作要求近期输入过密码,否则跳转密码确认页同为「操作级再认证」;本平台用可插拔因子集替代仅密码
Google 敏感操作验证关键变更要求重新输入密码或二次验证同上;Google 按操作枚举,本平台按用途码(权限码)配置
AWS MFA-protected APIAPI 调用附带 GetSessionToken 签发的临时凭据码同为「凭据随请求提交」;本平台凭据经专用响应头重放
Azure AD Claims Challenge服务端下发 claims challenge 要求客户端重新认证同为「服务端驱动的按需再认证」;本平台以 403 扩展字段承载挑战信息

OAuth 生态有标准化轨道:RFC 9470(OAuth 2.0 Step-Up Authentication Challenge Protocol)在 §3 “Authentication Requirements Challenge” 定义了资源服务器以 401 + WWW-Authenticate: Bearer error="insufficient_user_authentication" 携带 acr_values / max_age 参数要求客户端重新认证。本平台有意偏离该轨道:面向第一方 SPA,没有重定向回路和多授权服务器互操作需求,选择 403 + RFC 9457 ProblemDetails 扩展字段(purpose、factors、windowSeconds)换取更丰富的引导载荷。互操作边界:第三方若要对接,需要实现本平台的 403 契约而非 RFC 9470 客户端逻辑。

架构:一个强制点,三条来源

核心设计只有一句话:业务端点声明意图,全局中间件执行强制。声明与执行分离,端点零挂载、零感知——不存在任何端点级 Step-Up 过滤器,前端也不会遇到两套行为。

Redis挑战闭环端点IStepUpGrantServiceIStepUpPolicyEvaluatorStepUpEnforcementMiddleware第一方 SPARedis挑战闭环端点IStepUpGrantServiceIStepUpPolicyEvaluatorStepUpEnforcementMiddleware第一方 SPAPOST /api/business/sensitive(无凭据)1用途解析(Attribute 优先,否则端点权限码)2EvaluateAsync(purpose)3StepUpEnforced(因子集 + 窗口)4403 type=step-up-requiredpurpose + factors + windowSeconds5POST /api/identity/step-up/challenge6写挑战记录(TTL=120s)7challengeId + 因子集(含脱敏目标)8POST .../otp/send(邮箱/短信因子)9resendAllowInSeconds 倒计时10POST .../verify(challengeId + 因子 + 验证码)11校验并签发凭据(GET/SET + 版本键 SETNX)12stepUpToken + windowSeconds + expiresAt13重放业务请求(X-Step-Up-Token 头)14ValidateAsync(租户+主体+用途+撤销版本)15窗口 GET / 一次性 GETDEL16校验通过17放行进入业务 Handler18

读图要点(对照源码 BitzOrcasVNext 仓库 src/Hosts/BitzOrcas.Api/StepUp/StepUpEnforcementMiddleware.cs):

  1. 用途解析在内存索引上完成(图中节点 2)。端点元数据里的 RequireStepUpAttribute 声明优先;没有声明则取端点权限码,且只有当该权限码”可能被强制”(在冻结目录内)才继续。未命中强制面的请求只剩一次字典查找,没有数据库或 Redis 成本。
  2. 策略评估可豁免(节点 3)。Feature 总闸关闭、用途未被任何策略行或基线强制、机器调用者、AuditOnly 观察模式都会在这里放行;观察模式放行的同时写 StepUpAuditOnlyObserved 安全审计——这是灰度的数据来源。
  3. 信任边界在凭据校验(节点 18–20)。凭据绑定租户 + 主体 + 用途 + 撤销版本四元组,Redis 中的版本计数器在登出、改密、MFA 变更时递增,使所有在途凭据立即失效。任何存储异常都按”策略不可用”403 拒绝(fail-closed),绝不降级放行。
  4. 挑战闭环是独立端点族(节点 6–16,/api/identity/step-up/*),自身跳过再认证拦截,机器调用者与受限会话(强制改密、MFA 登记中的令牌)在该路径上被直接拒绝——防御纵深收口在唯一强制点,而不是散在端点里。

强制中间件在管线中的位置由组合根保证(src/Hosts/BitzOrcas.Api/Program.cs):认证 → 委托令牌 → 强制改密 → MFA 登记 → 租户解析 → 会话校验 → 权限注水 → 授权 → 当前用户作用域 → Step-Up 强制 → 限流。它必须晚于权限注水(要用注水后的权限码)与受限会话中间件(不重复裁决),早于限流(引导型 403 不消耗业务限流配额)。

三层策略模型

一个用途最终”是否强制、窗口多长、允许哪些因子”,由评估器按固定优先级裁决(src/Platform/Identity/BitzOrcas.Identity.Application/Identity/StepUp/StepUpPolicyEvaluator.cs):

机器调用者(Application/System/MCP 个人令牌)→ 一票豁免
Feature 总闸 identity.stepup 关闭 → 一票豁免(全链路零行为)
第一层:策略行(租户行命中优先于 Host 基线行)
第二层:声明基线(端点 [RequireStepUp] 特性)
第三层:无行无基线 → no-policy 豁免

策略行是管理页可配置的数据面。同一用途可以有多行,解析规则有两条:

  • 跨特异性层级:只取命中的最高层。特异性从高到低为 指定成员(Subjects)> 指定角色(Roles)> 指定权限(Permissions)> 全员(AllUsers)。例:某用途同时有”全员 Required 900s”和”成员甲 Off”两行,成员甲命中 Off 行(最高特异性),被豁免——这是”个人豁免压制全员强制”的标准形态。
  • 同层级多行命中:取最严。强制级别 Required > AuditOnly > Off;同级同级别再取最小窗口;因子集取交集。

策略行本身受三层写入护栏约束:用途必须在启动冻结目录内;租户行窗口不得超过 Host 控制面的窗口上限;租户可配置面收窄为白名单时,白名单外用途拒绝创建;平台强制(Mandatory)用途只允许加严(不得 Off/AuditOnly、窗口不得超过基线、因子不得超出基线因子集)。违反护栏返回 Identity.StepUp.PolicyConflict,detail 携带逐字段原因。

这个模型已有生产实例:operations.sql-masking.manage(SQL 脱敏规则管理)经组合根代码声明清单挂了平台强制 + 一次性票据基线——任何租户都不能把它降级为观察模式;而 API Key 与 HMAC 客户端生命周期命令则完全由租户策略行驱动,零代码标记,作为”配置驱动覆盖面”的生产演示。

因子与验证内核

Step-Up 不发明新的验证器,六种因子全部复用 Identity 既有的验证内核(各因子的注册、时钟漂移与克隆检测语义见多因素认证):

因子wire 名验证内核适用面
TOTP 验证器totpMFA 连接器 TOTP 校验已绑定验证器的用户;默认基线因子
通行密钥fido2WebAuthn 断言(W3C Web Authentication Level 3)规划中:契约占位保留,管理面与弹窗不渲染,底层断言接通后开放
邮箱 OTPemailOtp邮件 OTP 服务未绑定 MFA 的降级通道;响应携带脱敏目标 l***@example.com
短信 OTPsmsOtp短信 OTP 服务同上,受 SIM 风险约束,建议仅作过渡
密码password密码哈希重验有密码的账号;交互成本最低
恢复码recoveryCode恢复码原子消费(TryConsume)丢失验证器时的自救通道

TOTP 底层为 RFC 6238(TOTP: Time-Based One-Time Password Algorithm),其 §5.2 讨论了时钟漂移下的验证窗口处理;HOTP 底层为 RFC 4226。本平台不重复定义这些算法参数,时钟策略与漂移窗口沿用 MFA 连接器实现。

场景走查:丢失验证器的自解锁

产品经理小陈只绑定了 TOTP,手机进水送修。她仍需在系统里发起一次付款确认(策略行允许 recoveryCode):

  1. 她点击确认,前端收到引导型 403,弹窗因子列表里出现”恢复码”选项;
  2. 她从密码管理器里取出一枚恢复码输入——恢复码是单行输入、没有重发按钮、大小写不敏感提示;
  3. 服务端 TryConsume 原子消费这一枚(并发下至多成功一次),签发再认证凭据;
  4. 前端提示”恢复码已消费”,请求重放成功;
  5. 账号安全页的”二次验证”卡片显示剩余枚数;低于 3 枚时出现低存量警示并引导重新生成。

如果恢复码也耗尽,剩下的通道是管理员侧的恢复流程(验证身份所有权、撤销受信设备、轮换恢复码),而不是任何客户端可触达的”绕过验证”。

安全语义

fail-closed 清单——以下情况一律拒绝,绝不降级放行:

  • 策略快照读取失败(Redis / 数据库异常)→ 403 Identity.StepUp.PolicyUnavailable;
  • 凭据存储异常 → 同上(中间件统一捕获,仅豁免取消与内存耗尽);
  • 凭据有效但版本键已消失(凭据 TTL 内被外部删除)→ 403 GrantInvalid + 高危审计 reason=versionKeyMissing;
  • 挑战闭环端点被机器调用者或受限会话访问 → 403 ChallengeNotAllowed。

主体版本撤销与 TTL 边界:每个(租户,主体)有一个撤销版本计数器,凭据签发时记录当时版本,校验时不一致即拒绝。登出、改密、MFA 变更事件把计数器 +1,在途凭据立即失效。计数器 TTL 按”窗口上限 + 挑战 TTL + 安全余量”动态计算(出厂 900 + 120 + 60 = 1080 秒),保证任何仍可能存活的凭据必有存活的版本键可查——这是”版本键缺失”成为攻击信号而非正常态的原因。

TOCTOU 与检查-执行边界:凭据校验发生在中间件、业务执行发生在 Handler,两者之间没有原子事务。一次性票据消费即失效,“校验通过但业务失败”时票据不回滚——这是有意取舍:重放窗口归零优先于业务重试体验,票据烧掉的损失由用户重新验证一次来承担。所以窗口选择建议:低频高危操作(支付、权限变更)用一次性票据(窗口 0)或短窗口;高频批量场景(一条清单里连续多条敏感操作)用 300–900 秒窗口,避免用户被反复打断。

前端红线:再认证凭据只存标签页内存,禁止写入 localStorage / sessionStorage——页面刷新自然清空,XSS 暴露面最小化。详见前端接入。

本手册地图

页面类型内容
快速接入教程一个业务端点从零接入的三条路径与预期失败
契约参考参考端点清单、请求响应 JSON、403 扩展字段、错误码全表、配置键
前端接入指南拦截器重放守卫、验证对话框状态机、SDK 用法
灰度与运维指南AuditOnly 灰度顺序、阈值调优、Runbook、故障 Q&A

相关主题:多因素认证 · 权限授权 · 操作员代理

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%