Limitación de Tasa, CORS y Cabeceras de Seguridad
El middleware de borde en servicios Go reduce el abuso, define la política de origen cruzado del navegador y establece cabeceras de respuesta que protegen a los clientes contra ataques web comunes.
Busca en todas las páginas de la documentación
El middleware de borde en servicios Go reduce el abuso, define la política de origen cruzado del navegador y establece cabeceras de respuesta que protegen a los clientes contra ataques web comunes.
Ninguna de estas medidas reemplaza la autenticación o la validación de entrada, pero reducen el radio de explosión cuando algo más está mal configurado.
La limitación de tasa (rate limiting) limita cuántas solicitudes puede hacer un cliente en una ventana de tiempo, protegiendo la CPU, las conexiones a la base de datos y las cuotas de los sistemas posteriores.
CORS (Cross-Origin Resource Sharing) indica a los navegadores qué JavaScript de origen cruzado puede leer respuestas; no bloquea clientes que no sean navegadores.
Las cabeceras de seguridad instruyen a los navegadores para que apliquen HTTPS, restrinjan el framing y limiten la carga de scripts y recursos.
Implementa las tres como envolturas de http.Handler que aplicas consistentemente, incluso en rutas de error.
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
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)
})
}Cuándo usar esto:
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
}Lo que esto demuestra:
Vary: Origin y manejo de preflight OPTIONS.El cubo de tokens permite ráfagas mientras mantiene una tasa promedio.
Los despliegues distribuidos necesitan Redis o límites a nivel de gateway; los mapas en memoria son solo por proceso.
Origin.Permitir * con credenciales es inválido.
Reflejar valores de Origin arbitrarios permite el robo de datos entre sitios con credenciales.
CSP reduce el impacto de XSS bloqueando scripts en línea a menos que tengan un nonce.
X-Content-Type-Options: nosniff detiene ataques de confusión de MIME.
| Cabecera | Propósito |
|---|---|
Strict-Transport-Security | Forzar HTTPS para visitas futuras |
Content-Security-Policy | Restringir fuentes de scripts, estilos y frames |
X-Frame-Options / frame-ancestors | Protección contra clickjacking |
X-Content-Type-Options | Prevenir confusión de MIME |
Referrer-Policy | Limitar fugas en la cabecera Referer |
Permissions-Policy | Deshabilitar características del navegador (cámara, geolocalización) |
| Nivel | Ubicación | Notas |
|---|---|---|
| Global | API gateway / CDN | Mejor para volumen de DDoS |
| Por servicio | Middleware Go | Costo detallado por ruta |
| Por usuario | Después del middleware de autenticación | Cuotas justas para niveles de pago |
| Puntos finales costosos | Envoltura del manejador | Límites más bajos en /export vs /health |
// Detrás de un proxy inverso: confía en X-Forwarded-For solo de IPs de LB conocidas
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: * no se puede usar con Allow-Credentials: true. Usa orígenes explícitos.Allow-Origin: r.Header.Get("Origin") dinámico sin lista de permitidos equivale a CORS abierto. Siempre mapea contra un conjunto.X-Forwarded-For solo cuando el proxy elimina los valores de cliente no confiables.http.Error omite el middleware si lo llamas antes de la envoltura. Asegúrate de que el middleware de cabeceras envuelva todo el árbol de manejadores.http://localhost puede confundir las pruebas locales. Habilita HSTS solo en oyentes TLS de producción.default-src 'self' bloquea scripts de CDN. Ajusta CSP por despliegue o usa nonces para scripts en línea.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Limitación de tasa de API gateway | Alto volumen, política uniforme | Necesitas lógica por inquilino que solo la aplicación conoce |
golang.org/x/time/rate | Limitador compatible con la biblioteca estándar | Ya usas middleware de framework |
| Middleware CORS de chi/gin/echo | CORS integrado en el router | Quieres cero dependencias adicionales |
| WAF de CDN | Firmas de ataques de borde | Sustituto de la autenticación de la aplicación (insuficiente por sí solo) |
No.
CORS es aplicado solo por los navegadores.
Siempre autentica y autoriza independientemente de la configuración de CORS.
Después del ID de solicitud y el registro, a menudo antes o después de la autenticación dependiendo de si los límites son por IP (antes) o por usuario (después).
Nunca después de los manejadores; debe cortocircuitar primero.
429 Too Many Requests con Retry-After cuando puedes estimar el tiempo de reinicio.
Registra y métrica la clave que se está limitando para análisis de abuso.
Generalmente no; los navegadores no deberían llamar a nombres de host internos.
Si existe una SPA interna de administración, permite explícitamente su origen en la lista de permitidos.
Usa interceptores con un limitador compartido con clave por metadatos (clave de API o asunto del certificado del par).
O aplica en el service mesh / gateway.
A menudo se establece en la capa de ingress.
Si TLS llega directamente al proceso Go, también establece HSTS allí para conexiones directas de clientes.
default-src 'none'; frame-ancestors 'none' indica a los navegadores que no traten las respuestas como contenido activo.
Combina con el Content-Type: application/json correcto.
httptest con cabecera Origin y método OPTIONS.
Verifica que Access-Control-Allow-Origin solo se aplique a valores permitidos en la lista.
Sí; una única envoltura securityHeaders mantiene la política consistente.
Documenta los valores de las cabeceras en el README de la plantilla de tu servicio.
Usa límites más estrictos y retroceso exponencial en /login para ralentizar el credential stuffing.
Considera CAPTCHA o límites de tasa de IdP para la autenticación orientada al usuario.
Obsoleto en navegadores modernos.
Invierte en CSP y en la codificación de salida correcta en su lugar.
gRPC-Web puede atravesar navegadores; CORS y CSP aún se aplican a la fachada HTTP.
Configura listas de permitidos para los orígenes del gateway gRPC-Web.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (GC predeterminado de Green Tea, go fix modernizers - verifica el parche al compilar), chi (última versión - verifica al compilar), gin (última versión - verifica al compilar), echo (última versión - verifica al compilar), google.golang.org/grpc (última versión - verifica al compilar), sigs.k8s.io/controller-runtime (última versión - verifica al compilar), kubebuilder (última versión - verifica al compilar), tinygo (última versión - verifica los objetivos de placa al compilar), wazero (última versión - verifica al compilar) y golangci-lint (última versión - verifica al compilar).
Revisado por Chris St. John·Última actualización: 16 jul 2026