一、风险与目标风险:越权字段访问、过度抓取与复杂查询导致资源耗尽、输入污染。目标:字段级Scope授权、统一深度与复杂度上限、操作名与持久化查询强制。二、字段Scope映射与授权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) } function authorizeFields(tok: Token, fields: string[]): boolean { for (const f of fields) { const need = fieldScope[f] if (need && !hasScope(tok, need)) return false } return true } 三、查询深度与复杂度限制function maxDepth(query: string): number { let depth = 0 let max = 0 for (const ch of query) { if (ch === '{') { depth++; if (depth > max) max = depth } else if (ch === '}') { depth = Math.max(0, depth - 1) } } return max } function estimateComplexity(query: string): number { const m = query.match(/[a-zA-Z_][a-zA-Z0-9_]*/g) || [] const ignore = new Set(['query','mutation','subscription','fragment','on','true','false']) let count = 0 for (const w of m) if (!ignore.has(w)) count++ return count } 四、字段提取与校验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 } 五、持久化查询与操作名type Persisted = { id: string; text: string } function requireOperationName(name: string | undefined): boolean { return typeof name === 'string' && name.length >= 3 && name.length <= 64 } function matchPersisted(text: string, db: Record<string, Persisted>, id: string | undefined): boolean { if (!id) return false const item = db[id] if (!item) return false return item.text === text } 六、统一拦截示例type Req = { body: { query: string; operationName?: string; persistedId?: string }; token: Token } type Res = { status: (n: number) => Res; end: (b?: string) => void } function guardGraphQL(req: Req, res: Res, persisted: Record<string, { id: string; text: string }>) { const q = req.body.query || '' const name = req.body.operationName const id = req.body.persistedId if (!requireOperationName(name)) return res.status(400).end('invalid_operation') const depth = maxDepth(q) if (depth > 8) return res.status(400).end('depth_exceeded') const complexity = estimateComplexity(q) if (complexity > 100) return res.status(400).end('complexity_exceeded') if (!matchPersisted(q, persisted, id)) return res.status(400).end('unknown_query') const fields = extractFields(q, Object.keys(fieldScope)) if (!authorizeFields(req.token, fields)) return res.status(403).end('forbidden') } 七、验收清单深度≤8与复杂度≤100;操作名长度3-64;只接受持久化查询。字段级Scope映射生效,未授权字段拒绝访问;输入不含非法标识符。输出与执行路径可审计并关联操作名与持久化查询ID。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
2.476959s