如何在Go的gqlgen库中限制GraphQL查询的别名数量?
在gqlgen中限制GraphQL查询的字段重复调用(防范批量攻击)
问题背景
使用gqlgen搭建GraphQL服务器时,需要限制同一字段通过别名重复调用的次数来防范批量攻击。现有FixedComplexityLimit仅能限制查询复杂度,无法满足复杂Schema下的别名重复调用限制需求,需要类似JS社区graphql-no-alias包的功能。
需求示例
以下查询因重复调用productsByIds字段(使用别名productsByIds2),应触发错误:
query { productsByIds(productIds: "353573855") { active { id path title } productsByIds2: productsByIds(productIds: "353573855") { active { id path title } } }
以下查询调用不同字段,可正常执行:
query { productsByIds(productIds: "353573855") { active { id path title } products { active { id path title } } }
解决方案:自定义GraphQL验证规则
gqlgen支持通过自定义ValidationRule实现字段重复调用限制,核心思路是在查询验证阶段统计同一字段的调用次数,超过阈值则返回错误。
1. 实现自定义验证规则
创建验证规则,检查查询中同一字段的重复调用次数(包含别名场景):
import ( "context" "fmt" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/validator" ) // MaxFieldRepeatRule 限制同一字段在同一查询层级的最大重复调用次数 type MaxFieldRepeatRule struct { MaxAllowed int // 允许的最大重复次数,设为1则禁止同一字段重复调用 } func (r *MaxFieldRepeatRule) Validate(ctx context.Context, v *validator.ValidationContext) error { // 仅处理查询类型操作 opDef, ok := v.Operation.(*ast.OperationDefinition) if !ok || opDef.Operation != ast.Query { return nil } // 统计当前层级每个字段的调用次数 fieldCallCount := make(map[string]int) for _, selection := range opDef.SelectionSet.Selections { field, ok := selection.(*ast.Field) if !ok { continue // 跳过片段引用等非字段选择 } // 取字段原始名称(别名不影响,只统计实际调用的字段) fieldName := field.Name.Value fieldCallCount[fieldName]++ if fieldCallCount[fieldName] > r.MaxAllowed { return fmt.Errorf("字段 '%s' 重复调用次数超过限制(最多允许%d次)", fieldName, r.MaxAllowed) } } // 可选:递归检查嵌套字段的重复调用(根据需求开启) // r.validateNestedFields(opDef.SelectionSet.Selections) return nil } // 可选:递归验证嵌套字段 func (r *MaxFieldRepeatRule) validateNestedFields(selections []ast.Selection) { for _, selection := range selections { field, ok := selection.(*ast.Field) if !ok || field.SelectionSet == nil { continue } nestedCount := make(map[string]int) for _, nestedSel := range field.SelectionSet.Selections { nestedField, ok := nestedSel.(*ast.Field) if !ok { continue } fieldName := nestedField.Name.Value nestedCount[fieldName]++ if nestedCount[fieldName] > r.MaxAllowed { v.ReportError(validator.ValidationError{ Message: fmt.Sprintf("嵌套字段 '%s' 重复调用次数超过限制", fieldName), Nodes: []ast.Node{nestedField}, }) } } // 继续递归更深层级 r.validateNestedFields(field.SelectionSet.Selections) } }
2. 在gqlgen中注册验证规则
在初始化gqlgen服务器时,将自定义规则添加到验证器中:
import ( "log" "net/http" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/validator" "your-project-path/generated" // 替换为你的gqlgen生成代码路径 ) func main() { // 初始化gqlgen配置 cfg := generated.Config{Resolvers: &yourResolvers{}} // 替换为你的Resolver实例 execSchema := generated.NewExecutableSchema(cfg) // 创建自定义验证器并添加规则 customValidator := validator.New() // 禁止同一字段重复调用(设为1),可根据需求调整数值 customValidator.AddRule(&MaxFieldRepeatRule{MaxAllowed: 1}) // 创建GraphQL服务器并绑定验证器 srv := handler.NewDefaultServer(execSchema) srv.SetValidator(customValidator) // 启动服务器 http.Handle("/query", srv) log.Fatal(http.ListenAndServe(":8080", nil)) }
规则说明
- 该规则默认检查查询根字段,若开启递归则覆盖所有嵌套字段,统计每个原始字段的调用次数,超过
MaxAllowed阈值时直接返回错误。 - 完全覆盖
graphql-no-alias的核心功能,且支持自定义允许的重复次数,适配不同业务场景。
内容的提问来源于stack exchange,提问作者Furkan Topaloğlu
相关产品推荐
相关产品推荐

