Расскажите о вашем опыте реализации аутентификации и авторизации в 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))
    })
}

Ответ 18+ 🔞

Ну и вот, сидишь такой, пишешь очередной сервис, а тут на тебе — надо решать, кто в дверь стучится и что ему можно трогать. Аутентификация и авторизация, блядь, классика жанра, ёпта! Один проверяет, ты ли это, а второй — не суёшь ли ты свои ручонки куда не надо. И тут начинается цирк.

Аутентификация, сука, — это как паспортный контроль. Три основных способа, которые у меня в арсенале:

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

Вот и вся магия, ёпта. Главное — секретный ключ не забудь куда подальше спрятать, а не хардкодить, как я тут для примера. А то придёт какой-нибудь хитрожопый пидорас и наделает делов.