---
title: GraphQL持久化查询与Allowlist门禁(SHA256/签名/回退)最佳实践
keywords:
- Persisted Query
- Allowlist
- SHA256
- 签名
- 回退策略
description: 通过SHA256持久化查询与Allowlist门禁、签名校验与受控回退,阻断任意查询注入并优化性能。
categories:
- 文章资讯
- 技术教程
---
背景与价值
持久化查询可减少负荷并防止注入。结合Allowlist与签名治理,保证入口仅执行受控查询。
统一规范
- 哈希标识:统一使用
sha256标识持久化查询。 - 门禁策略:只允许白名单哈希;可选签名校验。
- 受控回退:无哈希时可选择拒绝或受限回退。
核心实现
哈希与白名单
type Persisted = { hash: string; query: string }
const allow = new Map<string, string>() // hash -> query
function validHash(h: string): boolean { return /^[a-f0-9]{64}$/.test(h) }
function registered(h: string): boolean { return allow.has(h) }
签名校验(可选)
async function importKey(spki: ArrayBuffer): Promise<CryptoKey> { return crypto.subtle.importKey('spki', spki, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']) }
function enc(s: string): Uint8Array { return new TextEncoder().encode(s) }
async function verifySig(key: CryptoKey, payload: string, sigB64: string): Promise<boolean> { const sig = Uint8Array.from(atob(sigB64.replace(/-/g,'+').replace(/_/g,'/')), c => c.charCodeAt(0)).buffer; return crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, sig, enc(payload)) }
执行门禁
type Req = { headers: Record<string, string | undefined>; body: { hash?: string; query?: string; variables?: Record<string, any>; sig?: string } }
async function gate(req: Req, pub?: CryptoKey): Promise<{ ok: boolean; query?: string }> {
const h = req.body.hash || ''
if (!validHash(h)) return { ok: false }
if (!registered(h)) return { ok: false }
const q = allow.get(h)!
if (pub && req.body.sig) { const ok = await verifySig(pub, h, req.body.sig); if (!ok) return { ok: false } }
return { ok: true, query: q }
}
落地建议
- 默认仅接受持久化查询;必要时开启签名校验以防哈希被劫持。
- 无哈希请求按受控回退策略处理(例如拒绝或仅允许少量读查询)。
验证清单
- 哈希是否有效且命中白名单;签名是否验证通过;回退策略是否受控。

发表评论 取消回复