---

title: GraphQL输入授权与字段级访问控制最佳实践

keywords:

  • GraphQL
  • 字段级授权
  • Scope
  • 深度限制
  • 复杂度限制
  • 输入校验
  • Persisted Query
  • OperationName
  • 规则引擎

description: 建立字段级授权与输入约束的统一策略,包含Scope映射、查询深度与复杂度限制、操作名与持久化查询校验,并附可验证拦截与解析示例。

categories:

  • 文章资讯
  • 技术教程

---

一、风险与目标

  • 风险:越权字段访问、过度抓取与复杂查询导致资源耗尽、输入污染。
  • 目标:字段级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 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部