一、策略与上下文type Token = { sub: string; scope: string[] } const fieldScope: Record<string, string> = { email: 'read:email', ssn: 'read:ssn', balance: 'read:balance' } function hasScope(tok: Token, need: string): boolean { return tok.scope.includes(need) } 二、审计钩子type Audit = { id: string; userId: string; field: string; operation: string; persistedId?: string; timestamp: string } function nowIso(): string { return new Date().toISOString() } function auditField(userId: string, field: string, operation: string, persistedId?: string): Audit { return { id: String(Date.now()) + Math.random().toString(36).slice(2), userId, field, operation, persistedId, timestamp: nowIso() } } 三、守卫与联动function guardField(tok: Token, fields: string[]): boolean { for (const f of fields) { const need = fieldScope[f]; if (need && !hasScope(tok, need)) return false } return true } 四、聚合指标class Metrics { counts = new Map<string, number>() inc(key: string) { const n = (this.counts.get(key) || 0) + 1; this.counts.set(key, n) } get(key: string): number { return this.counts.get(key) || 0 } } function keyOf(operation: string, field: string): string { return `${operation}:${field}` } 五、整合示例type Req = { body: { query: string; operationName?: string; persistedId?: string }; token: Token } type Res = { status: (n: number) => Res; end: (b?: string) => void } function extractFields(query: string, allow: string[]): string[] { const out: string[] = []; for (const f of allow) { const re = new RegExp(`\\b${f}\\b`); if (re.test(query)) out.push(f) } return out } function resolverGuard(req: Req, res: Res, metrics: Metrics): boolean { const op = req.body.operationName || 'unknown' const id = req.body.persistedId const fields = extractFields(req.body.query, Object.keys(fieldScope)) if (!guardField(req.token, fields)) { return res.status(403).end('forbidden'), false } for (const f of fields) metrics.inc(keyOf(op, f)) return true } function resolverAudit(req: Req): Audit[] { const op = req.body.operationName || 'unknown' const id = req.body.persistedId const fields = extractFields(req.body.query, Object.keys(fieldScope)) return fields.map(f => auditField(req.token.sub, f, op, id)) } 六、验收清单字段级Scope策略联动拦截;未授权字段拒绝且记录审计。指标按`operation:field`聚合;持久化查询ID参与审计与追踪。Resolver钩子输出包含用户与字段、操作名与时间戳;与守卫协同生效。

发表评论 取消回复