一、评分信号与模型type Signal = { type: 'ip' | 'device' | 'geo' | 'rate' | 'password_reset'; value: string; weight: number } type Context = { userId: string; tenantId: string; ip: string; device: string; geo: string; rateHits: number } function calcSignals(ctx: Context): Signal[] { const out: Signal[] = [] if (!/^\b(?:\d{1,3}\.){3}\d{1,3}\b$/.test(ctx.ip)) out.push({ type: 'ip', value: 'invalid', weight: 10 }) if (/bot|crawler/i.test(ctx.device)) out.push({ type: 'device', value: 'bot', weight: 8 }) if (ctx.geo === 'unknown') out.push({ type: 'geo', value: 'unknown', weight: 5 }) if (ctx.rateHits > 10) out.push({ type: 'rate', value: String(ctx.rateHits), weight: 7 }) return out } function score(signals: Signal[]): number { return signals.reduce((s, x) => s + x.weight, 0) } 二、挑战策略与阈值type Challenge = 'none' | 'captcha' | 'mfa' | 'step_up' function decideChallenge(s: number): Challenge { if (s < 5) return 'none' if (s < 12) return 'captcha' if (s < 20) return 'mfa' return 'step_up' } 三、决策与审计type Decision = { userId: string; tenantId: string; score: number; challenge: Challenge; timestamp: string } function nowIso(): string { return new Date().toISOString() } function decide(ctx: Context): Decision { const signals = calcSignals(ctx) const s = score(signals) const ch = decideChallenge(s) return { userId: ctx.userId, tenantId: ctx.tenantId, score: s, challenge: ch, timestamp: nowIso() } } 四、速率协同与中间件class RateGate { windowMs: number; max: number; hits = new Map<string, number[]>(); constructor(windowMs: number, max: number) { this.windowMs = windowMs; this.max = max } allow(key: string): boolean { const now = Date.now(); const arr = (this.hits.get(key) || []).filter(t => now - t < this.windowMs); if (arr.length >= this.max) return false; arr.push(now); this.hits.set(key, arr); return true } } type Req = { headers: Record<string, string | undefined> } type Res = { status: (n: number) => Res; end: (b?: string) => void; setHeader: (k: string, v: string) => void } function guard(req: Req, res: Res, gate: RateGate): Decision { const ctx: Context = { userId: req.headers['x-user-id'] || 'anon', tenantId: req.headers['x-tenant-id'] || 'anon', ip: req.headers['x-real-ip'] || '0.0.0.0', device: req.headers['user-agent'] || '', geo: req.headers['x-geo'] || 'unknown', rateHits: 0 } const key = ctx.tenantId + ':' + ctx.userId const ok = gate.allow(key) ctx.rateHits = ok ? 0 : 11 const d = decide(ctx) res.setHeader('X-Challenge', d.challenge) return d } 五、验收清单评分信号覆盖IP/设备/地理/速率等;阈值清晰且分层挑战;审计包含用户与租户、分数与挑战。与速率限制协同,过量触发提升挑战级别;中间件输出`X-Challenge`头与决策。参数与正则校验有效;可扩展其他信号(如密码重置频率、失败次数)。

发表评论 取消回复