Graphql

复杂度分析

学习如何在 NestJS GraphQL 中使用复杂度分析来防止恶意查询,包括配置复杂度限制和自定义复杂度计算。

复杂度分析

GraphQL 的强大功能之一是能够任意嵌套查询。虽然这为客户端提供了极大的灵活性,但它也为服务器带来了风险,因为单个查询可能会请求大量嵌套数据。

查询复杂度分析解决了这个问题,通过定义查询的复杂度并设置最大复杂度阈值来防止恶意查询。

安装

首先安装所需的包:

$ npm install graphql-query-complexity

开始使用

一旦安装过程完成,我们可以定义 ComplexityPlugin

import { GraphQLSchemaHost } from '@nestjs/graphql';
import { Plugin } from '@nestjs/apollo';
import {
  ApolloServerPlugin,
  GraphQLRequestListener,
} from '@apollo/server';
import { GraphQLError } from 'graphql';
import {
  fieldExtensionsEstimator,
  getComplexity,
  simpleEstimator,
} from 'graphql-query-complexity';

@Plugin()
export class ComplexityPlugin implements ApolloServerPlugin {
  constructor(private gqlSchemaHost: GraphQLSchemaHost) {}

  requestDidStart(): GraphQLRequestListener<any> {
    const { schema } = this.gqlSchemaHost;

    return {
      didResolveOperation({ request, document }) {
        const complexity = getComplexity({
          schema,
          operationName: request.operationName,
          query: document,
          variables: request.variables,
          estimators: [
            fieldExtensionsEstimator(),
            simpleEstimator({ maximumComplexity: 1000 }),
          ],
        });
        if (complexity >= 20) {
          throw new GraphQLError(
            `Query is too complex: ${complexity}. Maximum allowed complexity: 20`,
            {
              extensions: {
                code: 'QUERY_TOO_COMPLEX',
              },
            },
          );
        }
        console.log('Query Complexity:', complexity);
      },
    };
  }
}

为了演示,我们指定了 20 作为最大允许复杂度。在上面的示例中,我们使用了 2 个估算器,simpleEstimatorfieldExtensionsEstimator

简单估算器:简单估算器为每个字段返回固定复杂度

字段扩展估算器:字段扩展估算器从模式中提取复杂度值

现在,将此插件添加到提供者数组中:

@Module({
  providers: [ComplexityPlugin],
})
export class CommonModule {}

字段级复杂度

有时,我们需要为特定字段定义不同的复杂度值。我们可以使用 @Complexity() 装饰器来实现这一点。

@Resolver(() => Author)
export class AuthorResolver {
  @Query(() => Author)
  @Complexity(5)
  author() {
    return new Author(1, 'John', 'Doe');
  }

  @ResolveField()
  @Complexity(10)
  posts(@Parent() author: Author) {
    return [];
  }
}

@Complexity() 装饰器从 @nestjs/graphql 包导入。

使用 Mercurius

Mercurius 集成了 mercurius-validation 插件,该插件提供查询复杂度分析功能。要启用它,请将 validation 选项传递给 GraphQLModule#forRoot() 方法:

GraphQLModule.forRoot<MercuriusDriverConfig>({
  driver: MercuriusDriver,
  typePaths: ['./**/*.graphql'],
  validation: { complexity: { maximumComplexity: 10 } },
}),

complexityvalidation 选项都从 mercurius-validation 包导入。