Rate Limiting, CORS & Security Headers
Middleware de borda em serviços Go reduz abusos, define a política cross-origin do navegador e define cabeçalhos de resposta que endurecem clientes contra ataques web comuns.
Busque em todas as páginas da documentação
Middleware de borda em serviços Go reduz abusos, define a política cross-origin do navegador e define cabeçalhos de resposta que endurecem clientes contra ataques web comuns.
Nenhum desses substitui autenticação ou validação de entrada, mas eles reduzem o raio de explosão quando algo mais está mal configurado.
Rate limiting (limitação de taxa) limita quantas requisições um cliente pode fazer em uma janela de tempo, protegendo CPU, conexões de banco de dados e quotas downstream.
CORS informa aos navegadores quais JavaScripts cross-origin podem ler respostas - ele não bloqueia clientes não-navegadores.
Security headers (cabeçalhos de segurança) instruem os navegadores a impor HTTPS, restringir framing e limitar o carregamento de scripts e recursos.
Implemente os três como wrappers http.Handler que você aplica consistentemente, inclusive em caminhos de erro.
Cartão de receita de referência rápida - pronto para copiar e colar.
func securityHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
w.Header().Set("Content-Security-Policy", "default-src 'self'")
next.ServeHTTP(w, r)
})
}Quando usar isso:
package main
import (
"net/http"
"strings"
"sync"
"time"
)
var allowedOrigins = map[string]bool{
"https://app.example.com": true,
}
func cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
if allowedOrigins[origin] {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Vary", "Origin")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
w.Header().Set("Access-Control-Max-Age", "600")
}
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
type ipLimiter struct {
mu sync.Mutex
buckets map[string]*tokenBucket
}
type tokenBucket struct {
tokens float64
last time.Time
}
func (l *ipLimiter) allow(key string, rate float64, burst float64) bool {
l.mu.Lock()
defer l.mu.Unlock()
b, ok := l.buckets[key]
now := time.Now()
if !ok {
b = &tokenBucket{tokens: burst, last: now}
l.buckets[key] = b
}
elapsed := now.Sub(b.last).Seconds()
b.tokens = min(burst, b.tokens+elapsed*rate)
b.last = now
if b.tokens < 1 {
return false
}
b.tokens--
return true
}
func rateLimit(l *ipLimiter, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := strings.Split(r.RemoteAddr, ":")[0]
if !l.allow(ip, 5, 10) {
w.Header().Set("Retry-After", "1")
http.Error(w, "too many requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
func securityHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
w.Header().Set("Content-Security-Policy", "default-src 'self'; frame-ancestors 'none'")
next.ServeHTTP(w, r)
})
}
func main() {
limiter := &ipLimiter{buckets: make(map[string]*tokenBucket)}
mux := http.NewServeMux()
mux.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"ok":true}`))
})
handler := securityHeaders(cors(rateLimit(limiter, mux)))
http.ListenAndServe(":8080", handler)
}
func min(a, b float64) float64 {
if a < b {
return a
}
return b
}O que isso demonstra:
Vary: Origin e tratamento de preflight OPTIONS.O bucket de tokens permite rajadas enquanto mantém a taxa média.
Implantações distribuídas precisam de limites em nível de Redis ou gateway - mapas em memória são apenas por processo.
Origin.Permitir * com credenciais é inválido.
Refletir valores de Origin arbitrários permite roubo de dados cross-site com credenciais.
CSP reduz o impacto do XSS bloqueando scripts inline, a menos que sejam nonced.
X-Content-Type-Options: nosniff impede ataques de MIME sniffing.
| Cabeçalho | Propósito |
|---|---|
Strict-Transport-Security | Forçar HTTPS para visitas futuras |
Content-Security-Policy | Restringir fontes de script, estilo e frame |
X-Frame-Options / frame-ancestors | Proteção contra clickjacking |
X-Content-Type-Options | Prevenir confusão de MIME |
Referrer-Policy | Limitar vazamento no cabeçalho Referer |
Permissions-Policy | Desabilitar recursos do navegador (câmera, geolocalização) |
| Nível | Localização | Notas |
|---|---|---|
| Global | Gateway de API / CDN | Melhor para volume de DDoS |
| Por serviço | Middleware Go | Custo granular por rota |
| Por usuário | Após middleware de autenticação | Quotas justas para níveis pagos |
| Endpoints caros | Wrapper do handler | Limites menores em /export vs /health |
// Atrás de proxy reverso: confie em X-Forwarded-For apenas de IPs de LB conhecidos
func clientIP(r *http.Request, trusted bool) string {
if trusted {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
return strings.TrimSpace(strings.Split(xff, ",")[0])
}
}
host, _, _ := net.SplitHostPort(r.RemoteAddr)
return host
}Access-Control-Allow-Origin: * não pode ser usado com Allow-Credentials: true. Use origens explícitas.Allow-Origin: r.Header.Get("Origin") dinâmico sem lista de permissões é igual a CORS aberto. Sempre mapeie contra um conjunto.X-Forwarded-For apenas quando o proxy remover valores de cliente não confiáveis.http.Error ignora o middleware se você o chamar antes do wrapper. Garanta que o middleware de cabeçalhos envolva toda a árvore de handlers.http://localhost pode confundir testes locais. Habilite HSTS apenas em listeners TLS de produção.default-src 'self' bloqueia scripts de CDN. Ajuste CSP por implantação ou use nonces para scripts inline.| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Limites de taxa de gateway de API | Alto volume, política uniforme | Você precisa de lógica por inquilino que apenas o aplicativo conhece |
golang.org/x/time/rate | Limitador amigável à biblioteca padrão | Você já usa um middleware de framework |
| Middleware CORS chi/gin/echo | CORS integrado ao roteador | Você quer zero dependências extras |
| WAF de CDN | Assinaturas de ataque de borda | Substituir autenticação de aplicativo (insuficiente sozinho) |
Não.
CORS é aplicado apenas pelos navegadores.
Sempre autentique e autorize independentemente das configurações de CORS.
Após o ID da requisição e logging, frequentemente antes ou depois da autenticação, dependendo se os limites são por IP (antes) ou por usuário (depois).
Nunca depois dos handlers - ele deve cortar primeiro.
429 Too Many Requests com Retry-After quando você pode estimar o tempo de reinício.
Registre e métricas a chave que está sendo limitada para análise de abuso.
Geralmente não - navegadores não devem chamar nomes de host internos.
Se existir uma SPA administrativa interna, permita explicitamente sua origem.
Use interceptores com um limitador compartilhado com chave por metadados (chave de API ou assunto do certificado do par).
Ou imponha no service mesh / gateway.
Frequentemente definido na camada de ingress.
Se o TLS atingir o processo Go diretamente, defina HSTS lá também para conexões diretas de clientes.
default-src 'none'; frame-ancestors 'none' sinaliza aos navegadores para não tratar respostas como conteúdo ativo.
Combine com Content-Type: application/json correto.
httptest com cabeçalho Origin e método OPTIONS.
Afirme Access-Control-Allow-Origin apenas para valores permitidos.
Sim - um único wrapper securityHeaders mantém a política consistente.
Documente os valores dos cabeçalhos no README do seu template de serviço.
Use limites mais rigorosos e backoff exponencial em /login para desacelerar o credential stuffing.
Considere CAPTCHA ou limites de taxa de IdP para autenticação voltada para o usuário.
Obsoleto em navegadores modernos.
Invista em CSP e codificação de saída correta em vez disso.
gRPC-Web pode atravessar navegadores; CORS e CSP ainda se aplicam à fachada HTTP.
Configure listas de permissões para origens do gateway gRPC-Web.
Versões de Stack: Esta página foi escrita para Go 1.26.x (GC padrão Green Tea, go fix modernizers - verifique o patch na compilação), chi (última versão - verifique na compilação), gin (última versão - verifique na compilação), echo (última versão - verifique na compilação), google.golang.org/grpc (última versão - verifique na compilação), sigs.k8s.io/controller-runtime (última versão - verifique na compilação), kubebuilder (última versão - verifique na compilação), tinygo (última versão - verifique os alvos de placa na compilação), wazero (última versão - verifique na compilação) e golangci-lint (última versão - verifique na compilação).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026