Безопасность
SLOzy реализует многоуровневую систему безопасности: JWT-аутентификация, CORS, rate limiting и защитные заголовки.
JWT-аутентификация
Аутентификация построена на JSON Web Token. Access-токен живёт 8 часов (настраивается через JWT_EXPIRATION), refresh-токен — 7 дней.
JWTSecret: getEnv("JWT_SECRET", "dev_change_me_in_production_min_32_chars"),
JWTExpiration: getEnv("JWT_EXPIRATION", "8h"),Токены подписываются HMAC-SHA256. Refresh-токены хранятся в таблице user_sessions и позволяют безопасно продлевать сессию без повторного ввода пароля.
Эндпоинты аутентификации
| Метод | Путь | Описание |
|---|---|---|
| POST | /api/v1/auth/register | Регистрация, возвращает access + refresh токены |
| POST | /api/v1/auth/login | Вход, возвращает токены |
| POST | /api/v1/auth/refresh | Обновление токенов |
| POST | /api/v1/auth/logout | Инвалидация сессии на сервере |
| GET | /api/v1/auth/me | Информация о текущем пользователе |
CORS
CORS-мидлвар проверяет заголовок Origin и сравнивает со списком разрешённых источников (CORS_ORIGINS).
func CORSMiddleware(allowedOrigins []string) gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
// проверка по белому списку
if allowed {
c.Header("Access-Control-Allow-Origin", origin)
}
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Max-Age", "86400")
}
}По умолчанию разрешены http://localhost:3000 и http://localhost:8080. Для production указывается домен приложения: https://slozy.net.
Rate Limiting
Реализован алгоритм Token Bucket. Два уровня ограничений:
Глобальный лимит
| Параметр | Значение |
|---|---|
| Лимит | 1000 запросов в минуту |
| Burst | 50 запросов |
Лимит на IP
| Параметр | Значение |
|---|---|
| Лимит | 100 запросов в минуту |
| Burst | 10 запросов |
При превышении возвращается 429 Too Many Requests:
{
"error": "Rate limit exceeded",
"limit": 100,
"window": "1 minute",
"message": "Too many requests from your IP address"
}В ответ добавляются заголовки: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
IP-вайтлист и блэк-лист
IP-адреса из IP_WHITELIST пропускаются без проверки лимитов. Адреса из блэк-листа блокируются с 403 Forbidden.
Security Headers
Минимальный набор заголовков, устанавливаемых на каждый ответ:
| Заголовок | Значение | Назначение |
|---|---|---|
X-Frame-Options | DENY | Защита от clickjacking |
X-Content-Type-Options | nosniff | Запрет MIME-sniffing |
X-XSS-Protection | 1; mode=block | Защита от XSS (устаревший, но для совместимости) |
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload | HSTS — принудительный HTTPS |
Content-Security-Policy | default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss://localhost:* | CSP — контроль источников контента |
Referrer-Policy | strict-origin-when-cross-origin | Контроль Referer-заголовка |
Permissions-Policy | default-src 'self' | Ограничение API браузера |
Cross-Origin-Opener-Policy | same-origin | Изоляция COOP |
Cross-Origin-Embedder-Policy | require-corp | Изоляция COEP |
Server | SLOzy | Маскировка серверного ПО |
Request Size Limiter
Размер тела запроса ограничен 1 МБ. При превышении возвращается 413 Request Entity Too Large.
Логирование попыток входа
Неудачные попытки входа логируются в таблицу login_attempts:
INSERT INTO login_attempts (user_id, ip_address, user_agent, success, failure_reason, created_at)
VALUES ($1, $2, $3, $4, $5, NOW())Настройка безопасности
ENABLE_SECURITY=true
ENABLE_RATE_LIMIT=true
GLOBAL_RATE_LIMIT=1000
PER_IP_RATE_LIMIT=100
ENABLE_IP_WHITELIST=false
IP_WHITELIST=127.0.0.1,10.0.0.0/8
ALLOWED_ORIGINS=http://localhost:3000,https://slozy.net