Skip to content
bitzorcas
中EN

Guide

Identity 测试策略与生产运维

从聚合、应用流、持久化契约和 API Shell 到生产探针,建立 Identity 的测试金字塔、排障路径和安全事件处置手册。

Last updated

Identity 的测试不能只证明“正确密码能登录”。真正的交付证据来自失败分支、跨租户隔离、票据重放、平台 Token 隔离、适配器覆盖和生产告警。

1. 测试分层

聚合与纯策略测试
快、穷举状态边

Application 深模块测试
跨聚合编排与补偿

Store / ORM 契约测试
租户过滤、事务、查询等价

API Shell / Host 测试
认证、限流、序列化、失败关闭

生产探针与演练
通知、联邦、密钥、多实例 MFA

越往上越接近真实交付,也越慢。下层负责穷举状态,上层负责证明装配和基础设施没有改变语义。

2. 聚合与纯策略测试

测试类重点
UserAggregateTests改密刷新安全戳、失败锁定、启停、激活、Host 语义
OrganizationUnitAggregateTests路径、移动、后代判断、启停和排序
PlatformTenantLifecycleTests供给步骤、CompleteProvisioning、暂停和终态
TenantTransitionGuardTests所有合法边、同状态幂等、非法跳转错误码
PasswordHasherTestsBCrypt 验证与 Rehash 判断
PasswordValidatorTests每个复杂度规则的独立失败
FIDO2 / MFA testsCredential clone detection、缓存和风险策略

聚合测试使用固定 DateTimeOffset,不要依赖系统时间。业务错误断言 Error Code 和类型,不用本地化消息作为稳定契约。

3. 登录深模块测试

LoginFlowTests 直接驱动 ILoginFlow,当前覆盖:

  • 正常登录与 Token。
  • 用户 2FA 和风险 MFA。
  • MFA Challenge 签发失败时失败关闭。
  • Refresh Token Store 失败不返回成功。
  • Session Store 失败时撤销刚签发的 Refresh Token。
  • Captcha 首次挑战、正确答案、缺失答案、校验和生成失败。
  • 风险 Block 在密码前拒绝。
  • 错误密码、不存在用户、锁定、禁用和锁定到期自动解锁。
  • 风险引擎失败时当前的 fail-open 行为。
  • 强制改密标志、跨租户邮箱查找和取消传播。

运行这一组:

登录与风险回归
# 用 FullyQualifiedName 固定 Identity 深模块,不混入无关应用测试。
dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \
--configuration Release \
--no-restore \
--filter 'FullyQualifiedName~Identity.LoginFlowTests|FullyQualifiedName~Identity.LoginRiskIntegrationTests|FullyQualifiedName~Identity.VerifyMfaLoginCompletionTests'

修改签发顺序时,至少增加一个“前一步成功、后一步失败”的补偿测试。只有 Happy Path 无法证明不会泄露有效 Refresh Token。

4. 账号准入测试

AccountAdmissionFlowTests 是连续业务案例,不应拆成互不相干的 Mock 测试。它覆盖:

  1. 定向邀请只接受目标邮箱。
  2. 开放链接限制邮箱域、次数和到期时间。
  3. Approval 创建 PendingActivation,Activation 激活并可登录。
  4. 默认角色被应用,失效角色拒绝审批。
  5. Reject 后不能激活。
  6. 缺失或重放 Token 返回通用安全错误。

新增事务适配器后要在真实数据库验证:接受邀请失败时,Admission 与 UsedCount 不分叉;激活删除 Token 失败时,不能留下 Active 用户和可重放 Token。

5. 平台 Token 隔离

PlatformTokenIsolationTests 使用真实 JwtTokenService 和内存 Store,固定三条契约:

  • 刷新 Web Token 不改变 App 槽位。
  • 刷新 App Token 不改变 Web 槽位。
  • 新 RefreshTokenRecord 继承原始平台。
JWT Claim 与平台隔离
# 使用真实 JwtTokenService,固定 Claim 与 Web/App 隔离契约。
dotnet test tests/BitzOrcas.Unit.Tests/BitzOrcas.Unit.Tests.csproj \
--configuration Release \
--no-restore \
--filter 'FullyQualifiedName~Identity.TokenServiceClaimsTests|FullyQualifiedName~Identity.PlatformTokenIsolationTests'

生产 Store 必须再执行同一行为契约。内存 Dictionary 的隔离不能证明 ORM 唯一键和查询过滤器正确。

6. 持久化与双 ORM

Identity 同时包含统一聚合仓储和专用 Query Store。持久化测试至少覆盖:

契约SqlSugarEF Core
User TenantId 过滤必测必测
Host 用户全局查询必测必测
Invitation TokenHash 唯一查找必测必测
RefreshToken 平台 + Hash 索引必测必测
OU 树查询与分页必测必测
Admission + User + Token 事务必测必测

ProductionIdentityStoreTests、RepositoryIdentityStoreTests、IdentityQueryHandlerTests 与读模型 Adapter parity 测试是现有入口。新增 Store 不能只测“能保存”,还要测跨租户标识被拒绝和软删除数据不可见。

7. API Shell 边界

API Shell 没有生产数据库时使用失败关闭默认 Store,但路由仍应可启动。API 测试证明:

  • 接受邀请与完成激活不是 401;它们在 Store 不可用时返回业务/基础设施失败。
  • 邀请管理没有认证时是 401。
  • Login/Refresh 是匿名,Me/Logout 需要认证。
  • SmartEnum JSON 以名称序列化并可按名称或数值读取。
  • Result/Error 统一映射 Problem Details。
API Shell 安全边界
# Shell 不要求生产数据库,但必须证明公开与管理路由的认证边界。
dotnet test tests/BitzOrcas.Integration.Tests/BitzOrcas.Integration.Tests.csproj \
--configuration Release \
--no-restore \
--filter 'FullyQualifiedName~ApiShellTests'

公开端点“不是 401”不等于允许成功。缺少 Store、Token 无效或租户不可信时必须失败关闭。

8. 生产适配器门禁的覆盖范围

ProductionAdapterReadinessGuard 当前会阻止 NullUnitOfWork、InMemoryTenantStore、InMemoryApiClientStore 等默认实现进入 Production/Staging。它还检查 Redis、文件、通知 Publisher、Feature 和 Webhook 关键端口。

它当前没有完整覆盖 Identity 的 Email/SMS Delivery、IRefreshTokenStore、外部身份 Provider、MFA 配置 Store 和分布式 Challenge Cache。因此 Identity GA 还需要补充探针,不能把 Host 成功通过通用 Guard 当作全部就绪。

9. 观测信号

信号维度告警建议
登录失败率Tenant、Provider、Reason,不含用户名明文基线突增和密码爆破模式
风控降级次数风控错误码、实例任何持续增长都需告警
Captcha 生成/验证失败Provider、阶段Provider 故障与攻击流量分开看
MFA Challenge 失败方法、缓存实现多实例跨节点失败
Refresh Token 重用Tenant、UserId Hash、平台高优先级安全告警
会话写入补偿Store 错误码Token 已发后补偿风险
激活通知失败Channel、Provider、错误类入职流程阻塞
租户非法转换当前状态、目标状态运维脚本或并发问题

日志可记录 UserId 或 Token Hash 的诊断标识,但不得记录密码、Raw Token、完整手机号、JWT、Refresh Token 或连接字符串。

10. 登录故障排查

返回 InvalidCredentials

按顺序检查租户解析结果、Host/邮箱/租户内查找分支、用户状态和密码验证。对外保持通用错误,对内通过 CorrelationId 关联脱敏登录日志。

一直返回 Captcha

确认二次请求带回原 captchaChallengeId 与答案;检查票据是否被第一次校验消费;检查反向代理是否导致 IP、租户或设备上下文改变。

密码正确但没有 Token

检查 requiresMfa、requiresCaptcha、用户 TwoFactorEnabled 和风险 Challenge。再检查角色查询、Refresh Token Store 与 Session Store 的失败日志。

一个平台刷新导致另一个平台退出

检查 Token Name 是否为 RefreshToken:Web / RefreshToken:App,Store 唯一键是否包含 Name,轮换记录是否继承 Platform。

11. 邀请和激活故障排查

现象优先检查
所有邀请都 Invalid当前租户、Token Hash 算法、系统时间和 Store 查询
定向邀请邮箱不匹配规范化规则和候选人实际提交邮箱
审批后收不到邮件最终 IEmailDeliveryPort 类型、Frontend URL、供应商回执
激活一直 Invalid72h 到期、Token 是否被编码改变、UserToken Provider/Name
审批产生半成品用户UoW 范围、角色写入、通知失败和恢复脚本
重复激活成功Token 删除、Admission Consume 与并发唯一约束

修复数据前先保存审计证据。不要手工把用户直接改成 Active 而跳过 Admission 和 Token 状态;恢复操作应通过受审计用例或一次性运维命令执行。

12. 安全事件处置

疑似 Refresh Token 被盗

  1. 按 Token Hash 定位 RefreshTokenRecord 和族链。
  2. 撤销族链与相关 Session。
  3. 根据风险提升要求用户重新认证或重置密码。
  4. 检查不同平台是否受影响,不要无证据扩大范围。
  5. 保留平台、IP、DeviceId、时间和 CorrelationId,脱敏后进入安全审计。

邀请链接泄露

  1. 撤销 Invitation,保留 RevokedByUserId 和时间。
  2. 查询已产生 Admission 和审核状态。
  3. 对异常 Admission 执行拒绝;若已经激活,按账号事件处理。
  4. 创建新 Token,不能恢复旧 Raw Token。

JWT 签名密钥泄露

  1. 立即启用新 Kid,停止旧密钥签发。
  2. 视事件等级缩短或取消旧密钥验证宽限。
  3. 撤销 Refresh Token 与高风险会话。
  4. 验证所有实例已加载同一密钥环。
  5. 记录轮换证据并复盘 Secret 来源。

13. 发布前全量命令

Terminal window
# Restore 和 Build 使用同一 Release 输入,先固定依赖与编译产物。
dotnet restore BitzOrcas.Modern.slnx --locked-mode
dotnet build BitzOrcas.Modern.slnx --configuration Release --no-restore
# 分层执行 Identity 相关测试,失败时能直接定位责任层。
dotnet test tests/BitzOrcas.Unit.Tests/BitzOrcas.Unit.Tests.csproj \
--configuration Release --no-build --no-restore \
--filter 'FullyQualifiedName~Identity|FullyQualifiedName~Platform.Tenancy'
dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \
--configuration Release --no-build --no-restore \
--filter 'FullyQualifiedName~Identity'
dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj \
--configuration Release --no-build --no-restore \
--filter 'FullyQualifiedName~Identity'

Integration Tests 是否能使用 --no-build 取决于前面的解决方案构建是否包含目标;CI 应使用仓库已经验证的标准入口。

14. 全局扫尾与预期

Terminal window
# 不应出现复制登录编排或在 Host 直接验证密码。
rg -n "VerifyPasswordFailed|DummyPasswordHash|IssueMfaChallenge" \
src/Hosts src/Platform/Identity -g '*.cs'
# 除 LoginFlow 外的命中必须逐项解释,不能形成第二套密码登录状态机。
# Identity 统一聚合不应出现一对一 Entity/Mapper 复制。
find src/Platform/Identity -type f \
\( -name 'UserEntity.cs' -o -name 'AccountInvitationEntity.cs' -o -name 'PlatformTenantEntity.cs' \)
# 预期没有输出;若出现,必须有记录在 docs/architecture 的非对称例外。

15. GA 签字清单

  • 所有上层测试通过,且真实 ORM/消息/缓存/通知适配器参与集成测试。
  • 风控 fail-open 是经过产品和安全批准的显式策略。
  • 改密是否撤销全部会话有明确产品决定,测试与 UI 一致。
  • 通知、外部身份、MFA、Refresh Token Store 的最终实现可观测且非占位。
  • 跨租户、Host、平台 Token、票据重放和状态终态均有负向测试。
  • 密钥泄露、Token 盗用、邀请泄露和半成品审批都有演练手册。

返回 Identity 总览 · 配置与集成 · 生产安全清单

100%

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