RiskControl 对一次登录环境做可解释评估,输出 None、Captcha、MFA、StepUp 或 Block。它提高自动化攻击与异常登录的成本,但不替代密码校验、账户状态、授权、限流或审计。
1. 模块边界
| 层 | 主要类型 | 职责 |
|---|---|---|
| Contracts | RiskAssessment、RiskPolicy、IRiskFactor、Captcha 端口 | 稳定模型、窄端口和 AOT JSON 契约 |
| Application | RuleBasedRiskEngine、四个因子、策略 Provider、持久化 Pipeline | 评估、策略解析和事务外留痕 |
| Infrastructure | RiskAssessmentStore | 统一聚合写入与 QueryShape 读模型 |
| Identity | LoginHistoryProvider、LoginFlow、风险驱动 MFA | 提供登录事实并执行 Challenge |
| API / Website | Captcha Filter、Website Captcha Endpoint | HTTP 适配与匿名表单保护 |
模块没有独立的风控规则编辑 Endpoint,也没有机器学习模型、地理异常或弱密码因子。当前实现是可扩展的确定性规则引擎。
2. 登录主路径
LoginFlow 在密码验证前评估。Block 可节省 BCrypt 并抵御爆破;Captcha 先返回挑战再二次提交;MFA 与 StepUp 当前都进入现有 MFA 流;None 继续正常认证。
3. 当前四个因子
| 因子 | 权重 | 命中条件 | 证据 |
|---|---|---|---|
| NewIp | 20 | IP 非空且不在最近 30 分钟已知 IP | 原始 IP、已知数量 |
| NewDevice | 15 | DeviceInfo / UserAgent 不在已知设备 | 原始指纹、已知数量 |
| HighFrequency | 40 | 最近 5 分钟失败次数不少于 3 | 失败次数、阈值 |
| OffHours | 10 | 业务本地时间 00:00–06:00 | 小时、完整业务时间 |
四者全部命中最高 85 分。因此默认策略的 Block 区间 91–100 仅靠内建因子不可达;需要扩展因子或调整租户阈值。文档不能把 Block 描述成当前默认信号自然可触发的常见路径。
详见 规则引擎与风险因子。
4. 默认策略
| 分数 | Level | Challenge | Login 行为 |
|---|---|---|---|
| 0–30 | Low | None | 继续密码校验 |
| 31–50 | Medium | Captcha | 生成或校验图形验证码 |
| 51–75 | High | MFA | 密码通过后要求 MFA |
| 76–90 | Critical | StepUp | 当前同样映射到 MFA |
| 91–100 | Block | Block | 密码校验前拒绝 |
租户可以通过 riskcontrol.policy 整体覆盖区间与 Challenge。读取、JSON 解析或业务校验失败时回退默认策略并记录 Warning;取消请求仍向上传播。
详见 租户策略与证据持久化。
5. Captcha 能力与真实接线
模块提供 ImageCode、Slider、PointSelect 和可选 Behavior Provider。自有 Provider 使用五分钟票据、随机 nonce、盐渍 SHA-256 和 Redis GETDEL 防重放。
实际接线并不等于四类都在业务中使用:
- LoginFlow 与 Website 都调用
ResolveDefault(),因此使用 ImageCode; - Behavior 只有配置 Provider 时注册,否则解析 Behavior 会静默回退 ImageCode;
- Slider 和 PointSelect 已注册,但未发现业务 Endpoint 选择它们;
- 通用
RequireCaptchaEndpointFilter没有挂载到任何端点; - 该 Filter 只拒绝 Result Failure,错误答案返回 Success(Passed=false) 时仍放行;
- Login 二次提交没有验证 ChallengeId 是否属于当前 tenant + pseudo-session;Website 有前缀校验。
6. 评估事实为什么独立提交
Login.Command 实现 IRiskAssessedRequest。引擎把一次成功评估捕获到 Scoped Execution Context;外层 RiskAssessmentPersistencePipelineBehavior 在业务事务提交、失败返回或抛异常之后,用新的 Unit of Work 写入 SysRiskAssessment。
try{ // 内层包含登录事务;成功、失败 Result 或异常都先完成业务语义。 return await next(message, cancellationToken);}finally{ // 风险事实使用独立非取消事务,避免随登录失败一起回滚。 await PersistPendingAssessmentAsync();}记录失败只写 Error Log,不覆盖登录响应或原始异常。这保护可用性,却也意味着安全证据可能丢失;GA 应增加指标、告警与耐久补偿路径。
7. 查询、租户与隐私
GET /api/risk/assessments 需要 risk-control.assessment.view。Handler 从 ICurrentUser.User.TenantId 取租户,Store 又显式加入 TenantId Predicate,并将分页、排序和标量投影下推到当前 ORM。
两个边界需要明确:
- 正式租户模拟时 CurrentUser 仍可能是操作人原租户,而 Effective CurrentTenant 已是目标租户,查询可能落到错误租户;
- API Item 返回完整 Factors,其中当前含原始 IP、设备指纹和业务时间,没有脱敏投影。
8. 失败策略不是一个全局开关
| 故障点 | 当前策略 |
|---|---|
| 风险因子、历史或引擎异常 | Engine 返回 Failure;Login 记录 Warning 后无挑战放行 |
| 风控设置不可读或非法 | 回退默认策略 |
| Login Captcha 生成 / Provider Failure | 登录失败 |
| Captcha 答案错误 | Login 与 Website 拒绝;通用 Filter 当前误放行 |
| Redis 未注册 | NoOp Ticket Store;生成看似成功,后续验证必失败 |
| 风险评估事实写入失败 | 回滚独立 UoW、写日志、保留业务结果 |
| 风险历史快照损坏 | 查询返回稳定 Failure |
安全设计必须逐场景决定可用性与风险,而不是笼统写“风控失败关闭”。
9. 章节地图
| 目标 | 页面 |
|---|---|
| 理解历史快照、并行因子、分数与可达性 | 规则引擎与风险因子 |
| 正确生成、绑定和消费四类挑战 | Captcha Provider |
| 配置租户策略并理解独立持久化 | 策略与持久化 |
| 接入 Login、MFA、Website 与查询 API | 集成边界 |
| 建立安全、隐私、故障和生产门禁 | 测试与生产运维 |
10. 源码审查入口
# 先审查引擎、因子与实际 Login 消费,不能只看 Contracts。rg -n "RuleBasedRiskEngine|IRiskFactor|RequiredChallenge|AssessAsync" \ src/Platform/RiskControl src/Platform/Identity -g '*.cs'
# 查找 Captcha 的生成、校验与 Endpoint 接线。rg -n "GenerateAsync|VerifyAsync|RequireCaptchaEndpointFilter|ResolveDefault" \ src/Platform/RiskControl src/Platform/Identity src/Platform/Website src/Hosts -g '*.cs'
# 当前预期 Filter 没有 Endpoint 挂载,并且没有检查 Passed。rg -n "AddEndpointFilter<RequireCaptchaEndpointFilter>|GetValueOrThrow\\(\\)\\.Passed" \ src/Hosts/BitzOrcas.Api -g '*.cs'11. GA 最低证据
- 通用 Filter 修复
Passed判断并真正挂载到目标 Endpoint; - Login、Website、Filter 都验证 Challenge 的 tenant / session / scenario 绑定;
- 四个自有 Provider、Resolver、Redis 失败和第三方适配器都有真实合同测试;
- 行为 Provider 不在 URL 传 Secret,并校验 Provider、Scene、Randstr、UserIp 与响应协议;
- Risk Engine fail-open 有配置化风险接受、指标、告警和高价值场景例外;
- 评估查询使用 EffectiveTenantId,并对 IP、设备等证据做权限分级与脱敏;
- 独立持久化失败进入耐久告警或补偿,而不只是一条日志。