如何在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
相关产品推荐
相关产品推荐

