Ответ
Я реализовывал как аутентификацию (проверка личности пользователя), так и авторизацию (проверка прав доступа). В основном использовал следующие подходы:
Аутентификация
- 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))
})
}