Rate Limiting no Spring Security: Proteja suas APIs
Aprenda a implementar rate limiting no spring security para proteger suas APIs Java contra sobrecarga de CPU.

Como Blindar suas APIs de Ataques de Força Bruta com OAuth e Spring Security

Muitas equipes de desenvolvimento acreditam que o uso de tokens JWT no protocolo OAuth garante a proteção total dos seus microsserviços. No entanto, um único cliente autenticado de forma legítima consegue derrubar toda a sua infraestrutura ao realizar requisições em loops infinitos.

De acordo com dados reais da Cloudflare, os ataques de negação de serviço na camada de aplicação cresceram de forma exponencial nos últimos anos. Sem o devido bloqueio de chamadas excessivas, o banco de dados da sua aplicação sofrerá gargalos severos de processamento.

Além disso, o custo financeiro com o escalonamento automático de contêineres no Kubernetes pode disparar sem controle. Por isso, a implementação de uma barreira de controle de taxa tornou-se um requisito obrigatório para qualquer projeto corporativo sério.

Neste artigo, você vai aprender a implementar uma solução robusta utilizando Spring Security e a biblioteca Bucket4j. Dessa forma, você protege seu ambiente Java contra abusos e garante a estabilidade das suas APIs sob qualquer carga de tráfego.

Por que sua API precisa de Segurança contra Acessos Abusivos

A segurança de uma API vai muito além da simples autenticação e autorização de usuários. Se a sua aplicação Java recebe milhares de requisições idênticas em poucos segundos, os recursos do servidor vão se esgotar rapidamente.

Nesse cenário de estresse, a experiência do usuário final é severamente prejudicada devido à lentidão nas respostas. O Spring Security atua como a primeira linha de defesa no ecossistema Spring Boot, interceptando as requisições antes mesmo que elas cheguem aos seus controladores de negócio.

Para complementar essa proteção na camada de infraestrutura, recomendamos a leitura do nosso guia sobre o AWS WAF e Spring Boot: Blindando APIs contra OWASP Top 10. Essa combinação de filtros na aplicação e regras na borda da nuvem cria uma barreira de segurança extremamente difícil de ser superada.

Algoritmo Token Bucket: O Motor do Controle de Taxas para Java e Spring

O algoritmo Token Bucket é a solução mais utilizada pelo mercado para controle de tráfego de rede. Ele funciona com base em um balde virtual que armazena uma quantidade máxima de tokens de autorização de forma dinâmica.

Cada requisição enviada pelo cliente consome exatamente um token desse balde virtual. Se o balde ficar vazio, a requisição é imediatamente rejeitada com o status HTTP 429 Too Many Requests.

Os tokens são repostos em uma taxa constante e predefinida ao longo do tempo. Portanto, esse método permite picos ocasionais de tráfego sem que a aplicação trave, mantendo a experiência do usuário fluida e previsível.

Algoritmo Vantagem Principal Desvantagem Comum Melhor Caso de Uso
Token Bucket Suporta picos de tráfego legítimos Requer controle de estado em memória APIs públicas e microsserviços complexos
Fixed Window Fácil implementação e baixo consumo de CPU Permite dobro de tráfego na virada de janela Sistemas monolíticos simples
Sliding Window Log Extrema precisão no tempo de bloqueio Alto consumo de memória RAM Sistemas críticos de alta segurança financeira

Configurando Dependências para Segurança com Bucket4j no Spring Boot

Para iniciarmos a nossa jornada técnica, precisamos adicionar as dependências corretas no arquivo pom.xml do seu projeto Maven. Usaremos a biblioteca Bucket4j, que é uma das soluções mais eficientes para Java no controle de concorrência.

Esta biblioteca se integra de forma transparente ao Spring Security, permitindo a criação de filtros customizados na cadeia de execução. Além disso, ela apresenta excelente compatibilidade com ambientes de produção de alta escala.

Copie e cole o trecho de código abaixo para importar as bibliotecas necessárias para o seu ecossistema Java:


<dependency>
    <groupId>com.github.vladimir-bukhtoyarov</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>7.6.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

Lembre-se sempre de conferir as últimas versões estáveis de cada dependência no repositório central do Maven. Essa boa prática evita bugs conhecidos de segurança e problemas de incompatibilidade com novas versões do Spring Boot.

Desenvolvendo o Filtro Customizado para o Spring Security

Para interceptar as requisições autenticadas pelo OAuth, nós vamos criar um filtro Java que herda da classe OncePerRequestFilter. Essa classe do Spring Security garante que o nosso filtro seja executado exatamente uma vez por requisição HTTP.

Neste filtro, nós extraímos a identificação do cliente autenticado a partir do contexto de segurança do Spring. Em seguida, associamos essa identificação a um balde de tokens exclusivo para aquele usuário específico.

Abaixo, apresentamos uma implementação elegante utilizando o mapa concorrente do Java para gerenciar os baldes em memória de forma altamente performática:


package com.seusite.seguranca.filter;

import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Refill;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class RateLimitFilter extends OncePerRequestFilter {

    private final Map<String, Bucket> cache = new ConcurrentHashMap<>();

    private Bucket criarNovoBalde() {
        Bandwidth limite = Bandwidth.classic(50, Refill.intervally(50, Duration.ofMinutes(1)));
        return Bucket.builder().addLimit(limite).build();
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        
        String clientId = SecurityContextHolder.getContext().getAuthentication().getName();
        Bucket bucket = cache.computeIfAbsent(clientId, key -> criarNovoBalde());

        if (bucket.tryConsume(1)) {
            filterChain.doFilter(request, response);
        } else {
            response.setStatus(429);
            response.setContentType("application/json");
            response.getWriter().write("{\"error\": \"Muitas requisicoes. Tente novamente mais tarde.\"}");
        }
    }
}

Com essa implementação direta, seu sistema passa a controlar de forma dinâmica o fluxo de entrada. Portanto, qualquer ataque de força bruta será neutralizado antes que cause impacto na estabilidade da aplicação.

Integrando o Filtro de Rate Limiting na Cadeia do OAuth

Depois de criar o filtro, você precisa registrá-lo dentro do componente de configuração do Spring Security. Devemos posicionar o nosso filtro logo após o filtro de autenticação do OAuth.

Essa ordem de execução é fundamental para o sucesso da operação. Afinal, precisamos que o contexto de segurança já contenha o usuário identificado para sabermos qual balde de tokens aplicar.

Veja como estruturar a classe de configuração de segurança para ativar o filtro de forma integrada:


package com.seusite.seguranca.config;

import com.seusite.seguranca.filter.RateLimitFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> {}))
            .addFilterAfter(new RateLimitFilter(), UsernamePasswordAuthenticationFilter.class);
            
        return http.build();
    }
}

Essa configuração garante a segurança máxima para os seus microsserviços. O fluxo valida o token OAuth e, imediatamente após a aprovação, verifica os limites do usuário.

Escalando a Solução de Segurança no Kubernetes com Redis

A implementação baseada em ConcurrentHashMap apresentada anteriormente funciona perfeitamente para instâncias únicas de servidores Java. Contudo, em ambientes modernos que utilizam escalonamento automático no Kubernetes ou na nuvem, essa abordagem local falha.

Isso acontece porque um cliente pode ser direcionado para diferentes réplicas da aplicação através do balanceador de carga. Como resultado, o limite de requisições dele seria multiplicado pelo número de contêineres ativos.

Para solucionar esse problema clássico de arquitetura distribuída, substituímos o armazenamento local por um servidor Redis centralizado. O Bucket4j fornece suporte nativo ao Redis, permitindo sincronizar os baldes de tokens em tempo real de forma extremamente rápida.

Perguntas frequentes

O que é o HTTP Status 429?

Este código de status indica que o cliente enviou requisições demais em um determinado período de tempo. O servidor utiliza essa resposta para proteger a aplicação de sobrecargas e ataques de força bruta.

Como o Spring Security ajuda no rate limiting?

Ele intercepta todas as requisições HTTP antes que elas cheguem aos controladores da sua API. Dessa forma, você consegue validar a autenticação do token OAuth e aplicar a lógica do Bucket4j de maneira centralizada.

Por que o ConcurrentHashMap falha em ambientes de nuvem distribuídos?

Porque o mapa de concorrência armazena os dados na memória RAM local de uma única instância Java. Quando você escala sua aplicação no Kubernetes, os nós não compartilham essa memória, exigindo o uso de um cache centralizado como o Redis.

Conclusão

Garantir a segurança de suas APIs em cenários de alta concorrência exige a aplicação de técnicas modernas como o rate limiting. Integrar o controle de taxas ao Spring Security e ao fluxo de autenticação OAuth protege seus microsserviços contra abusos e ataques de negação de serviço.

Se você quer elevar o nível de proteção das suas aplicações empresariais e dominar novas ferramentas, comece hoje mesmo a reconfigurar sua esteira de desenvolvimento. Experimente implementar o filtro com Bucket4j em seu repositório de testes e sinta a diferença imediata na estabilidade do sistema.

Como o OAuth 2.0 e o Spring Security impedem ataques de força bruta

Ataques de força bruta representam uma ameaça constante para qualquer API Java. Hackers utilizam scripts automatizados para disparar milhares de requisições por segundo contra o endpoint de autenticação. O objetivo principal deles é adivinhar credenciais de acesso ou derrubar a sua infraestrutura de microsserviços por sobrecarga.

O framework Spring Security atua como a primeira linha de defesa da sua aplicação. Quando integramos o protocolo OAuth 2.0, o sistema valida tokens de acesso em vez de processar credenciais diretamente a cada chamada. Dessa forma, você evita consultas repetitivas ao banco de dados e reduz o consumo de memória do servidor.

No entanto, a validação de tokens por si só não impede o esgotamento de recursos do sistema. Por isso, implementar o controle de requisições na camada de segurança impede que usuários mal-intencionados façam chamadas abusivas. Essa estratégia garante a alta disponibilidade da API para os clientes legítimos.

Configuração prática do controle de tráfego com Bucket4j no Spring

A biblioteca Bucket4j é uma das ferramentas mais eficientes para implementar o algoritmo de token bucket em aplicações Java. Ela permite limitar o fluxo de requisições de forma flexível e resiliente. Para começar, precisamos adicionar a dependência do Bucket4j no arquivo pom.xml do seu projeto Maven.

Depois de configurar o projeto, nós criamos um filtro customizado dentro do ciclo de vida do Spring Security. Esse filtro intercepta as requisições antes que elas alcancem os controllers da sua API. O código abaixo demonstra como estruturar essa validação de forma simples.


// Exemplo conceitual de filtro de segurança no Spring
public class RateLimitFilter extends OncePerRequestFilter {
    private final Bucket bucket = Bucket.builder()
        .addLimit(Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1))))
        .build();

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws ServletException, IOException {
        if (bucket.tryConsume(1)) {
            chain.doFilter(req, res);
        } else {
            res.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            res.getWriter().write("Limite de requisições excedido.");
        }
    }
}

Dessa forma, o servidor rejeita imediatamente qualquer requisição que ultrapasse o limite configurado. De acordo com as diretrizes da OWASP, o bloqueio rápido na camada de rede ou filtro poupa processamento valioso da sua máquina virtual Java (JVM).

Estratégias de Rate Limiting para APIs corporativas

Diferentes cenários de negócios exigem abordagens distintas para o controle de tráfego. Por exemplo, você pode limitar as requisições com base no endereço IP do cliente ou no identificador exclusivo do token OAuth 2.0. A escolha da estratégia correta depende diretamente da arquitetura do seu sistema.

O controle por IP funciona muito bem para proteger endpoints públicos de autenticação e cadastro. No entanto, essa abordagem pode penalizar usuários legítimos que compartilham a mesma rede corporativa de internet. Por causa disso, identificar o usuário por meio do token de segurança é a opção mais recomendada para rotas autenticadas.

Abaixo, apresentamos uma comparação das principais abordagens utilizadas no mercado de desenvolvimento de software:

Estratégia Critério de Bloqueio Principal Vantagem Indicado Para
Por IP do Cliente Endereço de rede (IP) Protege antes da autenticação Endpoints de Login e Cadastro
Por Token OAuth ID do usuário no token Alta precisão por cliente APIs internas e Microsserviços
Híbrida IP + ID do Usuário Proteção em camadas profundas Sistemas financeiros e críticos

Benefícios de integrar proteção ativa ao Spring Cloud Gateway

Em arquiteturas de microsserviços, gerenciar limites individualmente em cada aplicação pode se tornar um pesadelo de manutenção. Portanto, centralizar essa proteção no Spring Cloud Gateway é uma decisão arquitetural extremamente inteligente. O gateway atua como um ponto único de entrada para todo o tráfego externo.

Além disso, o Spring Cloud Gateway possui integração nativa com o Redis para armazenar o estado dos limites de requisições. Essa integração permite que o controle funcione perfeitamente em ambientes clusterizados com múltiplas instâncias de servidores da API. Assim, você garante consistência na proteção mesmo sob intensa variação de carga.

Você pode conferir mais detalhes sobre boas práticas de arquitetura em nosso artigo sobre segurança em microsserviços. Dessa maneira, sua equipe constrói sistemas escaláveis e prontos para enfrentar ameaças externas sem perder performance.

Perguntas frequentes

O que acontece com a API do Spring Security quando o limite de requisições é excedido?

Quando o limite configurado é ultrapassado, o filtro do Spring Security interrompe imediatamente o fluxo da requisição. O servidor responde ao cliente com o código de status HTTP 429 (Too Many Requests) e interrompe o processamento. Essa resposta rápida economiza processamento e protege o banco de dados contra sobrecarga de conexões.

Como o Redis ajuda no controle de tráfego de servidores Java?

O Redis funciona como um banco de dados em memória compartilhado entre todas as instâncias da sua aplicação Java. Ele armazena o contador de requisições de forma centralizada e extremamente rápida. Com isso, o limite de acessos do usuário é mantido de forma consistente, mesmo se as requisições forem distribuídas por múltiplos servidores.

Qual é a diferença entre limitação por IP e limitação por usuário no OAuth 2.0?

A limitação por IP identifica a origem da conexão física, sendo ideal para proteger páginas públicas contra robôs. Por outro lado, a limitação por usuário lê o identificador único presente dentro do token de acesso do OAuth 2.0. Essa segunda abordagem é muito mais precisa, pois bloqueia apenas a conta do usuário abusivo, sem afetar outras pessoas na mesma rede.

Conclusão

Proteger suas APIs Java contra abusos e ataques de força bruta é um passo indispensável para garantir a estabilidade do seu negócio. Integrar o controle de tráfego com Spring Security e OAuth 2.0 cria uma barreira de defesa robusta e altamente escalável para os seus microsserviços. Quer garantir que sua plataforma esteja totalmente protegida contra essas vulnerabilidades? Fale hoje mesmo com um de nossos especialistas em segurança digital e agende uma análise completa da arquitetura do seu sistema.

Leia também