Skip to content
bitzorcas
中EN

Reference

再认证契约参考

敏感操作再认证全部公开表面:十条端点、挑战与验证 JSON 契约、三类 403 ProblemDetails、18 个错误码、StepUpOptions 配置键与限流配额。逐项对照源码核验。

Last updated

本页是可查不可猜的表面清单,所有类型、路径、默认值与约束均对照 BitzOrcasVNext 主干源码核验(契约定义 src/Platform/Identity/BitzOrcas.Identity.Contracts/Identity/StepUp/,端点声明分布于 .../Identity.Application/Commands|Queries/,强制点 src/Hosts/BitzOrcas.Api/StepUp/)。因子 wire 名全线使用 camelCase(totp、emailOtp),与 StepUpFactorWire 互转契约一致。

端点清单

方法与路径权限用途
POST /api/identity/step-up/challenge认证即可为指定用途发起挑战,返回允许因子集
POST /api/identity/step-up/otp/send认证即可对既有挑战触发邮箱/短信 OTP 投递
POST /api/identity/step-up/verify认证即可因子验证,成功签发再认证凭据
GET /api/identity/step-up/factors/{purpose}认证即可单用途策略决策预览(因子/窗口/是否强制)
QUERY /api/identity/step-up/my-purposes认证即可当前用户”将被要求二次验证”的用途投影
QUERY /api/identity/step-up/policiesidentity.stepup.manage列出租户行 + Host 基线行
QUERY /api/identity/step-up/policies/catalogidentity.stepup.manage用途目录投影(自建行选择器数据源)
POST /api/identity/step-up/policiesidentity.stepup.manage新建策略行
PUT /api/identity/step-up/policies/{id}identity.stepup.manage更新策略行(乐观并发)
GET / PUT /api/identity/step-up/policies/settingsidentity.stepup.manage(写仅 Host 上下文)窗口上限 + 租户可配置面控制面设置

挑战闭环三个端点自带跳过拦截标记,不会递归触发再认证。管理命令与查询还实现委托敏感语义:代理(模拟登录)会话不能替被代理用户改策略。

挑战与验证契约

发起挑战请求与成功响应(StepUpChallengeResponse):

// 请求体:用途码必须命中冻结目录(权限码或声明用途)。
{ "purpose": "identity.user.remove" }
// 200 响应:因子集 = 策略允许集 ∩ 用户当前可用能力。
{
"challengeId": "9fJ2vQ8rT3wL5nX1cZ7bA0dK4mH6pS2yE8uG1iO5aB3",
"expiresInSeconds": 120,
"windowSeconds": 300,
"factors": [
{ "type": "totp" },
{ "type": "emailOtp", "target": "l***@example.com" }
]
}
字段类型说明
challengeIdstring不透明挑战标识,32 字节 CSPRNG base64url,不可枚举
expiresInSecondsint挑战有效期(出厂 120 秒),过期须重新发起
windowSecondsint该用途当前策略窗口;0 表示一次性票据
factors[].typestring因子 wire 名
factors[].targetstring?脱敏投递目标,仅邮箱/短信因子返回

OTP 投递(StepUpOtpSendResponse):请求 { "challengeId": "…", "factorType": "emailOtp" },响应 { "resendAllowInSeconds": 60 }——到期前重复投递返回 429。

验证(StepUpVerifyResponse):请求 { "challengeId": "…", "factorType": "totp", "code": "482913" },响应:

// stepUpToken 即再认证凭据:32 字节 CSPRNG base64url,仅此一次完整返回。
// 校验失败消耗尝试次数(出厂每挑战 5 次),超限挑战作废(TooManyAttempts)。
{
"stepUpToken": "Ak7Qx2Wm9Rf4Tt1Lz8Nb0Vc5Yh3Jd6Pg2Se9Ua1Wr4X=",
"purpose": "identity.user.remove",
"windowSeconds": 300,
"expiresAt": "2026-09-02T08:15:00Z"
}

凭据经业务请求头重放,头名由 StepUp:TokenHeader 配置(出厂 X-Step-Up-Token)。

三类 403 ProblemDetails

所有 403 都是 RFC 9457 ProblemDetails,扩展字段平铺到响应根。与 RFC 9470 §3 的 401 + WWW-Authenticate 轨道的偏离及边界见概念页。

// 形态一:引导型(type 后缀 step-up-required)——前端据此拉起验证弹窗。
// 触发条件:无票据(GrantRequired)或窗口票据过期(GrantExpired)。
{
"type": "https://docs.bitzsoft.com/problems/step-up-required",
"title": "Step-Up Required",
"status": 403,
"detail": "该操作需要二次验证。",
"errorCode": "Identity.StepUp.GrantRequired",
"errorType": "Forbidden",
"purpose": "identity.user.remove",
"factors": [{ "type": "totp" }, { "type": "emailOtp", "target": "l***@example.com" }],
"windowSeconds": 300
}
// 形态二:拒绝型(type 后缀 step-up-invalid)——不带 factors,避免反复探测。
// errorCode 取 GrantInvalid 或 GrantPurposeMismatch。
{
"type": "https://docs.bitzsoft.com/problems/step-up-invalid",
"title": "Step-Up Invalid",
"status": 403,
"detail": "二次验证凭据与当前操作不匹配。",
"errorCode": "Identity.StepUp.GrantPurposeMismatch",
"errorType": "Forbidden"
}
// 形态三:策略不可用(fail-closed,绝不放行)。
{
"type": "https://docs.bitzsoft.com/problems/step-up-policy-unavailable",
"title": "Step-Up Unavailable",
"status": 403,
"detail": "安全策略暂不可用,操作已被拒绝。",
"errorCode": "Identity.StepUp.PolicyUnavailable",
"errorType": "Forbidden"
}

错误码全表

错误码HTTP触发场景
Identity.StepUp.ChallengeNotFound400挑战不存在、已过期或已消费
Identity.StepUp.ChallengeExpired400挑战超时失效
Identity.StepUp.CodeInvalid400验证码/恢复码/密码错误;消耗一次尝试
Identity.StepUp.TooManyAttempts400尝试次数超限,挑战作废
Identity.StepUp.FactorNotAllowed422请求因子不在允许集内
Identity.StepUp.FactorUnavailable422因子未绑定或不可用(如恢复码耗尽)
Identity.StepUp.EnrollmentRequired403无可用因子,需先完成 MFA 绑定
Identity.StepUp.RateLimited429挑战重发频控命中(含 retryAfterSeconds)
Identity.StepUp.GrantRequired403引导型:缺票据
Identity.StepUp.GrantInvalid403凭据无效:主体/租户不符、版本撤销、一次性重放、版本键缺失
Identity.StepUp.GrantExpired403引导型:窗口票据过期
Identity.StepUp.GrantPurposeMismatch403票据用途与端点不符;高危审计
Identity.StepUp.PolicyUnavailable403策略/凭据存储读取失败,fail-closed
Identity.StepUp.PolicyConflict422违反 Host 硬上限、白名单、Mandatory 加严或同范围去重
Identity.StepUp.PurposeUnknown422用途码不在启动冻结目录内
Identity.StepUp.ChallengeNotAllowed403机器调用者或受限会话触碰挑战闭环
Identity.StepUp.PolicyInvalid422策略行形状非法(因子集为空、范围值缺失或超长等)
Identity.StepUp.PolicyNotFound404策略行不存在或已删除
Identity.StepUp.PolicyVersionConflict422策略行并发版本冲突,刷新后重试
Identity.StepUp.PolicyHostOnly403控制面设置仅 Host 管理上下文可写

配置键(StepUp 节)

类型 StepUpOptions(StepUpOptions.cs),绑定节 StepUp,Host 启动时校验组合自洽。出厂默认是工程建议值而非外部规范条款,应随观察期数据调整:

键出厂默认约束
WindowDefaultSeconds300≥ 0;0 表示一次性票据
WindowCeilingSeconds900> 0;租户行窗口上限;≤ 技术防呆上限
WindowTechnicalMaxSeconds86400≥ 窗口上限;仅防误配,不是安全策略
ChallengeTtlSeconds120> 0
MaxAttemptsPerChallenge5> 0
OtpResendAllowSeconds60> 0
VersionKeySafetyPaddingSeconds60> 0;版本键 TTL = 上限 + 挑战 TTL + 余量
TokenHeaderX-Step-Up-Token非空白
PolicySnapshotTtlSeconds60> 0;策略快照兜底收敛窗口
RoleMembershipTtlSeconds300> 0;角色成员快照兜底收敛窗口

appsettings.json 示例(与出厂默认等价):

{
"StepUp": {
"WindowDefaultSeconds": 300,
"WindowCeilingSeconds": 900,
"WindowTechnicalMaxSeconds": 86400,
"ChallengeTtlSeconds": 120,
"MaxAttemptsPerChallenge": 5,
"OtpResendAllowSeconds": 60,
"VersionKeySafetyPaddingSeconds": 60,
"TokenHeader": "X-Step-Up-Token",
"PolicySnapshotTtlSeconds": 60,
"RoleMembershipTtlSeconds": 300
}
}

限流与存储键

挑战闭环四端点使用独立滑动窗口限流(节 RateLimiting:StepUp,分区 = 租户 + 主体 + 端点范围):

端点配置键出厂每分钟
challengeChallengePerMinute10
verifyVerifyPerMinute5
otp/sendOtpSendPerMinute5
factorsFactorsPerMinute30

Redis 键空间经统一缓存键构建器生成,形态 {app}:{env}:v1:stepup:…:

键形态语义TTL
global:ch:{challengeId}挑战记录挑战 TTL(120s)
global:ch:{challengeId}:attempts失败计数(Lua 原子递增,超限连删)随挑战
global:grant:{token}凭据:窗口模式 GET 只读、一次性模式 GETDEL 原子读删窗口秒数
global:tenant-{tid}:ver:{subjectKey}撤销版本计数器(INCR 递增 / SETNX 初始化)上限 + 挑战 TTL + 余量(出厂 1080s)

已知限制

  • fido2 因子为契约占位:枚举与 wire 名保留、管理面不渲染、挑战不签发,等 WebAuthn 断言接通后开放(规划中)。
  • 出厂基线的交付形态分两层:组合根代码声明清单已包含一个试点用途(operations.sql-masking.manage,平台强制 + 一次性票据),声明经接线校验必须有真实端点以同权限码承载,漂移即启动失败;演示作用域另含两个纯策略行用途的仅观察种子行(灰度起点,幂等不回拨)。其余用途无 Host 行时回退端点声明基线,基线行可经 Host「安全基线」页配置。
  • 验证停留超过访问令牌剩余寿命时,前端会放弃重放(重试命中内存缓存自愈);多标签页不共享凭据。

100%

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