Ответ
Я реализовывал как аутентификацию (проверка личности пользователя), так и авторизацию (проверка прав доступа). В основном использовал следующие подходы:
Аутентификация
- JWT (JSON Web Tokens): Наиболее частый выбор для stateless-сервисов. Сервер генерирует токен после успешного входа пользователя, а клиент прикрепляет его к каждому последующему запросу. Это избавляет сервер от необходимости хранить состояние сессии.
- OAuth 2.0: Использовал для интеграции с внешними провайдерами (например, "Войти через Google"). Работал с библиотекой
golang.org/x/oauth2. - Сессии на основе Cookie: Классический stateful-подход для веб-приложений, где идентификатор сессии хранится в cookie, а данные сессии — на сервере (например, в Redis).
Авторизация
- RBAC (Role-Based Access Control): Для разграничения прав доступа использовал роли. В сложных системах применял библиотеку Casbin, которая позволяет гибко настраивать политики доступа (кто, что и с каким ресурсом может делать).
Пример Middleware для проверки JWT
Этот middleware-обработчик извлекает JWT из заголовка Authorization, проверяет его подлинность и, в случае успеха, передает управление следующему обработчику, добавляя информацию о пользователе в контекст запроса.
import (
"context"
"net/http"
"strings"
"github.com/golang-jwt/jwt/v4"
)
// userCtxKey - ключ для хранения данных пользователя в контексте.
var userCtxKey = &contextKey{"user"}
type contextKey struct {
name string
}
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Authorization header required", http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
// В реальном приложении секретный ключ должен загружаться из конфигурации!
secretKey := []byte("your_super_secret_key")
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})
if err != nil || !token.Valid {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// Добавляем информацию из токена (например, claims) в контекст
ctx := context.WithValue(r.Context(), userCtxKey, token.Claims)
// Вызываем следующий обработчик с обновленным контекстом
next.ServeHTTP(w, r.WithContext(ctx))
})
} Ответ 18+ 🔞
Ну и вот, сидишь такой, пишешь очередной сервис, а тут на тебе — надо решать, кто в дверь стучится и что ему можно трогать. Аутентификация и авторизация, блядь, классика жанра, ёпта! Один проверяет, ты ли это, а второй — не суёшь ли ты свои ручонки куда не надо. И тут начинается цирк.
Аутентификация, сука, — это как паспортный контроль. Три основных способа, которые у меня в арсенале:
- JWT (JSON Web Tokens): Это когда после входа сервер плюёт тебе в лицо токеном, а ты потом с этим соплём ходишь и каждому встречному-поперечному его суёшь. Stateless, блядь, красота — серверу ничего помнить не надо, он просто смотрит на подпись и кивает: «Проходи, чмошник». Идеально для API.
- OAuth 2.0: Это когда лень свою регистрацию городить, и ты такой: «Идите нахуй, пусть Google/Гитхаб/ВК вас авторизует». В Го использовал
golang.org/x/oauth2— вроде норм, не развалилось. - Сессии на куках: Старый добрый способ, когда сервер тебе даёт билетик (session ID), а сам про тебя всё помнит. Данные сессии обычно в Redis пихаешь, чтобы память не жрать. Stateful, но для монолитных веб-приложений — то, что надо.
Авторизация — это уже про права. Тут часто RBAC (Role-Based Access Control) юзал. «Админ», «Юзер», «Гость» — всем своё. А когда политики доступа становятся сложнее, чем маршрут пьяного ежа, подключаешь тяжёлую артиллерию вроде Casbin. Эта библиотека, блядь, позволяет описать правила так: «Кто (роль), Что (ресурс), Как (действие)» — и потом просто спрашиваешь у неё: «Можно?», а она тебе: «Иди нахуй» или «Ладно, проходи».
А вот, смотри, кусок кода middleware для JWT. Он как швейцар у клуба: проверит твой токен и либо пропустит внутрь, либо пошлёт на три весёлые буквы. Код не трогаю, он святой.
import (
"context"
"net/http"
"strings"
"github.com/golang-jwt/jwt/v4"
)
// userCtxKey - ключ для хранения данных пользователя в контексте.
var userCtxKey = &contextKey{"user"}
type contextKey struct {
name string
}
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Authorization header required", http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
// В реальном приложении секретный ключ должен загружаться из конфигурации!
secretKey := []byte("your_super_secret_key")
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})
if err != nil || !token.Valid {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// Добавляем информацию из токена (например, claims) в контекст
ctx := context.WithValue(r.Context(), userCtxKey, token.Claims)
// Вызываем следующий обработчик с обновленным контекстом
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Вот и вся магия, ёпта. Главное — секретный ключ не забудь куда подальше спрятать, а не хардкодить, как я тут для примера. А то придёт какой-нибудь хитрожопый пидорас и наделает делов.