Se você tiver um endpoint Node.js GraphQL no back-end do seu projeto com vários resolvedores, e se você o tiver implementado em produção, você precisará proteger seus endpoints GraphQL API limitação de taxa e profundidade.
A limitação de taxa ajuda a controlar um usuário se um limite definido de solicitações por tempo for excedido, e a limitação de profundidade ajuda a limitar a complexidade de uma consulta GraphQL por sua profundidade. Essas medidas ajudam seu aplicativo a evitar spam de API e ataques de consulta. Neste artigo, vamos cobrir por que e como limitar a taxa e o limite de profundidade de suas APIs.
O que é limitação de taxa?
Limitar taxa significa limitar o número de chamadas de API de um aplicativo ou o usuário pode fazer em um determinado período de tempo. Se esse limite for excedido, o usuário ou cliente pode ser restringido, ou seja, o cliente pode ser proibido de fazer chamadas de API mais semelhantes no mesmo período.
Por que APIs de limite de taxa?
Seu servidor de back-end geralmente terá algumas limitações sobre a quantidade de solicitações que ele pode processar em um período de tempo. Muitas vezes, usuários com intenções mal-intencionadas bombardeiam seus endpoints de API com spam, o que desacelera seu servidor e pode até travá-lo.
Para proteger seus endpoints de API e servidor de sobrecarregar, você deve limitar a taxa de sua API endpoints, seja uma API REST ou um endpoint GraphQL.
Métodos de limitação de taxa
Existem várias maneiras de limitar APIs, como as seguintes.
Por endereço IP por período de tempo
Você pode limitar certos endereços IP e pode restringir o acesso deles aos seus serviços se excederem o número de solicitações de API dentro de um período de tempo.
Por usuário período de tempo do pe
Você pode limitar certos usuários em seu aplicativo (por seu identificador exclusivo em seu banco de dados) e restringir seu acesso aos seus serviços se excederem o número de solicitações de API dentro de um período de tempo.
Por endereço IP e usuário por período de tempo
Neste método, você estrangula um usuário se ele exceder o limite de taxa definido por você com base no fato de ele estar usando o mesmo endereço de IP necessário para fazer isso.
Limites de taxa uniforme para todos os resolvedores GraphQL
Faça isso quando os resolvedores GraphQL ( mutações, consultas e assinaturas) em um servidor têm o mesmo limite de taxa, como 10 solicitações por minuto por usuário.
Por exemplo, um servidor GraphQL pode têm signInMutation, getUserQuery e outros resolvedores dentro das mesmas regras de limitação de taxa, o que significa que há um limite de taxa uniforme em resolvedores GraphQL ).
Regras de limite de taxa diferentes por resolvedor GraphQL
Às vezes, O resolvedor GraphQL obtém uma regra de limitação de taxa diferente. Por exemplo, os resolvedores que exigem quantidades tediosas de memória e poder de processamento terão limites de taxa mais rígidos, em comparação com um resolvedor de fácil processamento e menos demorado.
Os limites de taxa são mais rígidos quando menos solicitações de API são permitido por período de tempo.
Armazenamento de dados de limitação de taxa
Para limitar a taxa de seus endpoints de API ou GraphQL, você precisa rastrear o tempo, IDs de usuário, endereços IP e/ou outros identificadores exclusivos , e você também precisará armazenar dados da última vez que o identificador solicitou o ponto de extremidade para calcular se o limite da taxa foi excedido pelo identificador ou não.
Um “identificador” pode ser qualquer um único string que ajuda a identificar um cliente, como um ID de usuário em seu banco de dados, um endereço IP, uma combinação de string de ambos ou até mesmo informações do dispositivo.
Então, onde você armazena todos esses dados?
Redis é o banco de dados mais adequado para esses casos de uso. É um banco de dados de cache onde você pode salvar pequenas informações em pares de chaves e é extremamente rápido.
Vamos instalar o Redis agora. Posteriormente, você poderá conectá-lo à configuração do servidor Node.js GraphQL para armazenar informações relacionadas à limitação de taxa.
Se você for usando os documentos oficiais para instalar o Redis , use estes comandos na linha de comando
wget http://download.redis.io/redis-stable.tar.gz tar xvzf redis-stable.tar.gz cd redis-stable make sudo cp src/redis-server/usr/local/bin/# copiando a compilação para os lugares apropriados sudo cp src/redis-cli/usr/local/bin/# copiando a compilação para os lugares apropriados
Pessoalmente, acho que existem maneiras mais fáceis de instalar:
Em um Mac:
brew install redis # para instalar redis redis-server/usr/local/etc/redis.conf # para executar redis
No Linux:
sudo apt-obtenha install redis-server # para instalar redis redis-server/usr/local/etc/redis.conf # para executar redis
Depois que o servidor Redis iniciar, crie um novo usuário. No localhost, não há problema em executar o Redis sem nenhuma senha, mas você não pode para produção porque não deseja que seu servidor Redis seja aberto para a Internet.
Agora, vamos configurar uma senha Redis. Execute redis-cli para iniciar a linha de comando do Redis. Isso só funciona se o Redis estiver instalado e em execução.
Em seguida, insira este comando na CLI:
config set requiredepass somerandompassword
Agora saia da linha de comando. Seu servidor Redis agora está protegido por senha.
Usando a limitação de taxa no GraphQL
Aqui, usaremos módulo npm graphql-rate-limit . Você também precisará de ioredis .
npm i graphql-rate-limit ioredis-s
Neste tutorial, estou usando o servidor graphql-yoga para o back-end. Você também pode usar Apollo GraphQL .
graphql-rate-limit funciona com qualquer configuração Node.js GraphQL. Tudo o que ele faz é criar diretivas GraphQL para usar em seu esquema GraphQL .
Esta é a aparência de um servidor GraphQL normal:
import {GraphQLServer} from’graphql-yoga’const typeDefs=`type Query {hello (name: String!): String! } `const resolvers={Query: {hello: (_, {name})=>` Hello $ {name} `}} const server=new GraphQLServer ({typeDefs, resolvers}) server.start (()=> console.log (‘Server is running on localhost: 4000’))
Agora, vamos limitar a taxa de consulta de boas-vindas (resolvedor) usando este código.
import {GraphQLServer} from’graphql-yoga’import * como Redis de”ioredis”import {createRateLimitDirective, RedisStore} de”graphql-rate-limit”export const redisOptions={host: process.env.REDIS_HOST ||”127.0.0.1″, porta: parseInt (process.env.REDIS_PORT) || 6379, senha: process.env.REDIS_PASSWORD ||”somerandompassword”, retryStrategy: times=> {//reconectar após retornar Math.min (times * 50, 2000)}} const redisClient=new Redis (redisOptions) const rateLimitOptions={describeContext: (ctx)=> ctx?.request ?.ipAddress || ctx?.id, formatError: ({fieldName})=> `Uau, você está fazendo muito $ {fieldName}`, store: new RedisStore (redisClient)} const rateLimitDirective=createRateLimitDirective (rateLimitOptions) resolvers={Query: {hello: (_, {name})=> `Hello $ {name}`}}//Schema const typeDefs=`diretiva @rateLimit (max: Int window: String message: String identityArgs: [String] arrayLengthField: String ) em FIELD_DEFINITION digite Consulta {olá (nome: String!): String! @rateLimit (window:”1s”, max: 2)} `const server=new GraphQLServer ({typeDefs, resolvers,//isso permite que você use a diretiva @rateLimit no esquema GraphQL. schemaDirectives: {rateLimit: rateLimitDirective}}) servidor.start (()=> console.log (‘Server is running on localhost: 4000’))
Pronto, o resolvedor de boas-vindas agora tem taxa limitada.
Se você visitar GraphQL Playground em https://localhost: 4000 , tente executar a consulta abaixo.
# Tente enviar spam para esta consulta clicando rápido, # você deverá ver uma mensagem de erro depois de atingir o limite da taxa. consultar {olá (nome:”Kumar Abhirup”)} Tente clicar no botão play branco rapidamente para enviar spam e você atingirá o limite de taxa.
Você deve ver a mensagem de erro definido depois de atingir o limite de taxa: Uau, você está dando muito alô.
Agora vamos analisar o código em detalhes.
Opções de limite de taxa no GraphQL
const rateLimitOptions={describeContext: (ctx)=> ctx?.request?.ipAddress || ctx?.id, formatError: ({fieldName})=> `Uau, você está fazendo demais $ {fieldName}`, store: new RedisStore (redisClient)} const rateLimitDirective=createRateLimitDirective (rateLimitOptions)
identityContext is uma função que retornará uma string exclusiva para todos os dispositivos ou um usuário no banco de dados, ou uma combinação de ambos. É aqui que você decide se deseja limitar a taxa por endereço IP, ID de usuário ou outros métodos.
No snippet acima, tentamos definir o endereço IP do usuário como o identificador exclusivo, e usa o contextID fornecido pelo servidor GraphQL padrão como um valor substituto se o endereço IP não for recuperado.
formatError é um método que permite formatar a mensagem de erro que um usuário vê depois de atingir a taxa limit.
store conecta-se à instância do Redis para salvar os dados de limitação de taxa necessários em seu servidor Redis. Sem uma loja Redis, esta configuração de limite de taxa não pode operar. Observe que você nem sempre precisa usar o Redis como uma loja-você pode usar MongoDB ou PostgreSQL também, mas esses bancos de dados são um exagero para uma solução simples de limitação de taxa.
createRateLimitDirective é uma função que, se fornecida com rateLimitOptions, permite que você crie diretivas de limitação de taxa dinâmica que você pode conectar posteriormente ao seu servidor GraphQL para uso em seu esquema.
Este é o esquema..
const typeDefs=`diretiva @rateLimit (max: janela Int: String mensagem: String identityArgs: [String] arrayLengthField: String) em FIELD_DEFINITION tipo Query {olá (nome: String!): String! @rateLimit (window:”1s”, max: 2)} `
diretiva @rateLimit cria o @rateLimit diretiva no esquema que aceita alguns parâmetros que ajudam a configurar as regras de limite de taxa para cada resolvedor .
Você pode pré-corrigir @rateLimit (janela:”1s”, máx: 2) para qualquer resolvedor assim que sua diretiva GraphQL estiver configurada e pronta para uso. janela:”1s”, máx: 2 significa que o resolvedor pode ser executado por um usuário, um endereço IP ou pelo identificador de texto especificado apenas duas vezes por segundo. Se a mesma consulta for executada pela terceira vez dentro desse período de tempo, o erro de limite de taxa aparecerá.
Felizmente, agora você sabe como os resolvedores GraphQL podem ter limitação de taxa, o que ajuda a evitar que o spam de consulta seja sobrecarregado seus servidores.
Agora que cobrimos a limitação da taxa de GraphQL, vamos ver como tornar seu endpoint GraphQL mais seguro com limitação de profundidade.
O que é limitação de profundidade GraphQL?
Limitação de profundidade refere-se a limitar a complexidade das consultas GraphQL por sua profundidade. Os servidores GraphQL costumam ter carregadores de dados para carregar e preencher dados usando banco de dados relacional consultas.
Observe a consulta abaixo:
livro de consulta {getBook (id: 1) {revisões do editor do autor do título {livro do corpo do título {revisões do editor do autor do título {title}}}}}
Busca resenha (ões) para o (s) livro (s) consultado (s). Cada livro tem rviews e cada revisão está conectada a um livro, tornando-o uma relação um-para-muitos que está sendo consultada pelo dataloader.
Agora, olhe para esta consulta GraphQL.
query badMaliciousQuery {getBook (id: 1) {reviews {book {reviews {book {reviews {book {reviews {book {reviews {book {reviews {book {# and so on…}}}}}}}}}}} }}}
Esta consulta tem vários níveis de profundidade. Ele cria um loop enorme, que pode continuar por um longo tempo, dependendo da profundidade da consulta, onde o livro busca resenhas e as resenhas buscam livros e assim por diante.
Tal string de consulta pode sobrecarregar seu GraphQL servidor e pode travá-lo. Imagine enviar uma consulta de 10.000 níveis de profundidade-seria desastroso!
É aqui que entra a limitação de profundidade. Ela permite que o servidor GraphQL detecte essas consultas e evite que sejam processadas como um aviso.
Há também outro método usado para resolver esse problema, chamado de erro “Tempo limite de solicitação esgotado”, que impede um resolvedor de realizar uma consulta se ela demorar muito para resolvê-la.
Limitação de profundidade da API GraphQL
Este é um processo bastante fácil. Podemos usar graphql-depth-limit para limitar a profundidade das consultas GraphQL.
import {GraphQLServer} from’graphql-yoga’import * as depthLimit from’graphql-depth-limit’const typeDefs=`type Query {hello (name: String!): String! } `const resolvers={Query: {hello: (_, {name})=>` Hello $ {name} `}} const server=new GraphQLServer ({typeDefs, resolvers,//facilmente definir um limite de profundidade em todos os consultas de entrada do Graphql//aqui, definimos um limite de profundidade de 7 validationRules: [depthLimit (7)]}) server.start (()=> console.log (‘Server is running on localhost: 4000’))Tudo pronto! Existem muitas maneiras adicionais de usar a limitação de profundidade para limitar a complexidade das consultas .
Conclusão
A limitação de taxa e a limitação de profundidade de seus endpoints GraphQL são essenciais para evitar que seu servidor GraphQL fique sobrecarregado com solicitações de API e também protege seu servidor contra ataques de consulta maliciosos que podem colocar seu resolvedores em um loop de solicitação sem fim, especialmente quando você está implantando o servidor para um aplicativo ativo.