Расскажите о вашем опыте реализации аутентификации и авторизации в Go-приложениях.

Ответ

Я реализовывал как аутентификацию (проверка личности пользователя), так и авторизацию (проверка прав доступа). В основном использовал следующие подходы:

Аутентификация

  1. JWT (JSON Web Tokens): Наиболее частый выбор для stateless-сервисов. Сервер генерирует токен после успешного входа пользователя, а клиент прикрепляет его к каждому последующему запросу. Это избавляет сервер от необходимости хранить состояние сессии.
  2. OAuth 2.0: Использовал для интеграции с внешними провайдерами (например, "Войти через Google"). Работал с библиотекой golang.org/x/oauth2.
  3. Сессии на основе 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))
    })
}