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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 23:45:40