You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在AWS AppSync Lambda授权器中解析GraphQL查询获取目标资源

解决AWS AppSync Lambda授权器的GraphQL查询结构化解析问题

问题背景

我正在为AWS AppSync API编写Lambda授权器,授权逻辑依赖用户访问的目标资源——每个资源都有独立的ACL,记录允许访问的用户及条件。

目前的方案是获取调用者身份后遍历所有ACL做授权,这种方式效率极低,完全不切实际。我原本的思路是先确定用户访问的目标资源,再获取对应ACL验证用户权限,但始终无法可靠提取目标资源:AWS返回的授权请求仅包含查询字符串和变量,用graphql-js转成AST后,结构冗长且多变,之前写的资源提取代码只适配AppSync控制台的查询格式,没法兼容Amplify前端的请求;而且不能依赖变量名,攻击者可以轻易构造任意名称的查询,甚至不使用变量。

我也曾考虑在Lambda解析器中实现授权逻辑,但这需要把解析器当作授权器来用,不够理想,而且我只需要最基础的解析逻辑,不需要实现完整的解析器能力。我期望能把GraphQL查询解析成如下结构化JSON:

/* Schema:
type Query {
    operationName(key: KEY!): responseType
}*/

/* Query:
query abitraryQueryName($var1: KEY!) {
  operationName(key: $var1) {
    field1
    field2
  }
}*/

/* Variables:
{ "var1": "value1" } */

parsedQuery = {
    operation: "operationName",
    params: { "key": "value1" },
    fields: [ "field1", "field2" ]
};

解决方案

1. 基于graphql-js实现轻量解析,无需完整解析器

可以利用graphql-js的Schema验证、变量替换能力,配合空解析器来提取结构化信息,不需要实现完整的业务解析逻辑:

核心步骤:

  • 定义与AppSync完全一致的Schema,用于验证查询和解析变量
  • 编写空的根解析器(仅匹配操作名,返回null即可)
  • 解析查询AST,提取操作名、替换变量后的参数,以及请求的字段列表

示例代码:

const { graphql, buildSchema, parse } = require('graphql');

// 1. 复用AppSync的Schema定义
const schema = buildSchema(`
type Query {
    operationName(key: String!): ResponseType
    getPost(postId: ID!): Post
}

type ResponseType {
    field1: String
    field2: String
}

type Post {
    id: ID
    title: String
    content: String
}
`);

// 2. 空根解析器,仅匹配操作名
const rootResolver = {
  operationName: () => null,
  getPost: () => null
};

// 3. 解析查询并生成结构化数据
async function parseGraphQLRequest(queryStr, variables = {}) {
  // 解析AST
  const ast = parse(queryStr);
  // 提取主操作节点
  const operationNode = ast.definitions.find(def => def.kind === 'OperationDefinition');
  if (!operationNode) return null;

  // 提取操作名(处理别名情况)
  let operationName = '';
  const mainField = operationNode.selectionSet.selections[0];
  if (mainField.kind === 'Field') {
    operationName = mainField.name.value;
    // 如果有别名,这里可以根据需求选择保留别名或取实际操作名
    // operationName = mainField.alias?.value || mainField.name.value;
  }

  // 提取并替换变量后的参数
  const params = {};
  if (mainField.arguments) {
    mainField.arguments.forEach(arg => {
      let argValue = arg.value;
      // 处理变量替换
      if (argValue.kind === 'Variable') {
        argValue = variables[argValue.name.value];
      } else {
        // 处理直接传入的字面量值
        argValue = argValue.value;
      }
      params[arg.name.value] = argValue;
    });
  }

  // 提取请求的字段列表
  const fields = [];
  if (mainField.selectionSet) {
    mainField.selectionSet.selections.forEach(sel => {
      if (sel.kind === 'Field') {
        fields.push(sel.name.value);
      }
    });
  }

  return {
    operation: operationName,
    params,
    fields
  };
}

// 测试调用
const testQuery = `query getResource($id: String!) {
  operationName(key: $id) {
    field1
    field2
  }
}`;
const testVariables = { "id": "resource-123" };

parseGraphQLRequest(testQuery, testVariables).then(result => {
  console.log(JSON.stringify(result, null, 2));
});

2. 兼容多种请求格式的AST解析优化

针对Amplify或其他客户端的请求,需要处理以下常见情况:

  • 别名处理:如果查询使用了别名(如myAlias: operationName(key: $var)),要提取实际的操作名而非别名
  • 嵌套字段:如果请求包含嵌套子字段,可根据需要提取到指定层级
  • 多操作查询:如果查询包含多个操作,遍历所有selectionSet中的字段分别处理

3. 结合AppSync请求上下文的辅助信息

作为AppSync的自定义授权器,Lambda会收到完整的请求上下文,其中request.operationName字段会返回客户端指定的操作名(如果客户端传了的话)。不过注意客户端可以不指定该字段,所以AST解析仍然是必要的 fallback 方案。

关键注意事项

  • 必须保证本地定义的Schema与AppSync的Schema完全一致,否则变量替换和参数提取会出错
  • 要覆盖所有GraphQL值类型(Int、ID、Boolean、InputObject等)的变量替换逻辑
  • 针对批量查询或Mutation,需要遍历所有操作节点分别解析权限

内容的提问来源于stack exchange,提问作者Iorpim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 10:30:52