Какие основные подходы к реализации аутентификации и авторизации существуют в Go?

Ответ

В Go, как и в других языках для бэкенда, важно различать два понятия:

  • Аутентификация (Authentication, AuthN) — это процесс проверки личности пользователя. Ответ на вопрос: «Кто вы?».
  • Авторизация (Authorization, AuthZ) — это процесс проверки прав доступа пользователя к определенным ресурсам. Ответ на вопрос: «Что вам разрешено делать?».

Существует два основных подхода к их реализации:

1. Token-based (на основе токенов)

Это самый популярный stateless-подход для современных API и микросервисов. Наиболее распространенный стандарт — JWT (JSON Web Token).

Процесс:

  1. Пользователь отправляет логин и пароль.
  2. Сервер проверяет их, и в случае успеха генерирует JWT, подписывая его секретным ключом.
  3. Токен возвращается клиенту, который сохраняет его (например, в localStorage или cookie).
  4. При каждом последующем запросе к защищенным ресурсам клиент отправляет этот токен в заголовке Authorization: Bearer <token>.
  5. Сервер проверяет подпись и срок действия токена. Если все в порядке, он извлекает из токена информацию о пользователе (ID, роли) и предоставляет доступ.

Пример с использованием github.com/golang-jwt/jwt/v5 (актуальная библиотека):

import (
    "time"
    "github.com/golang-jwt/jwt/v5"
)

var jwtKey = []byte("your_secret_key")

type Claims struct {
    UserID int64 `json:"user_id"`
    jwt.RegisteredClaims
}

// Генерация токена
func generateJWT(userID int64) (string, error) {
    expirationTime := time.Now().Add(24 * time.Hour)
    claims := &Claims{
        UserID: userID,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(expirationTime),
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(jwtKey)
}

// Валидация токена
func validateJWT(tokenStr string) (*Claims, error) {
    claims := &Claims{}
    token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
        return jwtKey, nil
    })

    if err != nil {
        return nil, err
    }

    if !token.Valid {
        return nil, errors.New("invalid token")
    }

    return claims, nil
}

2. Session-based (на основе сессий)

Это классический stateful-подход, часто используемый в монолитных веб-приложениях.

Процесс:

  1. Пользователь отправляет логин и пароль.
  2. Сервер проверяет их и создает сессию, сохраняя ее ID и данные пользователя у себя (в памяти, Redis, базе данных).
  3. Клиенту в cookie отправляется только идентификатор сессии.
  4. При каждом запросе браузер автоматически отправляет cookie с ID сессии.
  5. Сервер находит сессию по ID и получает данные пользователя.

Для этого подхода в Go часто используется пакет gorilla/sessions.

В современных веб-фреймворках (Gin, Echo, Chi) проверка токенов или сессий обычно выносится в middleware, который защищает определенные эндпоинты.

Ответ 18+ 🔞

А, слушай, вот эта тема про авторизацию и аутентификацию — это ж классика, блядь! Вечный спор, как и что делать, а в итоге все упирается в два простых вопроса, ёпта. Первый: «Ты кто такой, сука?» — это аутентификация. В второй: «А тебе сюда можно, пидорас?» — это уже авторизация. Вот и вся философия, в рот меня чих-пых!

Ну и способов это организовать — как говна за баней, но основных два, блядь. И они, сука, как два брата-близнеца, только один — умный, а второй — дурак, который всё хранит у себя в голове.

Первый способ — на токенах. Модный, молодёжный, stateless, блядь. Как JWT, например. Суть проще пареной репы:

  1. Ты приползаешь к серверу, сука, с логином и паролем.
  2. Сервер тебя проверяет и, если ты не конченый мудак, выдает бумажку (токен), которую сам же и подписал своим секретным ключом. Типа, «предъявителю сего разрешается не быть мудаком».
  3. Ты эту бумажку засовываешь куда подальше (в localStorage или куки) и таскаешь с собой.
  4. Дальше, когда лезешь куда-то закрытое, ты просто суёшь эту бумажку в заголовок: Authorization: Bearer <твой_токен_тут>.
  5. Сервер смотрит на подпись и срок годности. Если не просрочен и подпись его — значит свой, блядь, пускает. Всю инфу о тебе (ID, роли) он прямо из токена выковыривает. Удобно, ёпта!

Вот тебе кусочек кода на Go, чтоб не быть голословным, как мартышлюшка. Библиотека github.com/golang-jwt/jwt/v5, не хуйню какую-то:

import (
    "time"
    "github.com/golang-jwt/jwt/v5"
)

var jwtKey = []byte("your_secret_key") // Ключик, блядь, храни как зеницу ока!

type Claims struct {
    UserID int64 `json:"user_id"` // Вот твой ID, например
    jwt.RegisteredClaims
}

// Функция, которая штампует эти бумажки
func generateJWT(userID int64) (string, error) {
    expirationTime := time.Now().Add(24 * time.Hour) // Годен сутки, не больше
    claims := &Claims{
        UserID: userID,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(expirationTime),
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // Подписываем
    return token.SignedString(jwtKey)
}

// А это функция, которая проверяет, не подделка ли
func validateJWT(tokenStr string) (*Claims, error) {
    claims := &Claims{}
    token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
        return jwtKey, nil // Даём ключ на проверку подписи
    })

    if err != nil {
        return nil, err // Токен кривой, нахуй
    }

    if !token.Valid {
        return nil, errors.New("invalid token") // Невалидный, просроченный
    }

    return claims, nil // Всё чики-пуки, вот твои claims
}

Второй способ — сессии. Это как старый дед, который всё помнит, но память у него уже дырявая, блядь. Stateful подход.

  1. Ты опять лезешь с логином-паролем.
  2. Сервер создаёт у себя в базе или в Redis запись (сессию), а тебе в куки пихает только её ID — типа номерок в гардеробе.
  3. Потом, когда ты ходишь по сайту, браузер сам, сука, этот номерок в каждом запросе показывает.
  4. Сервер по номерку находит твою сессию, смотрит, кто ты, и решает, пускать ли.
  5. Для этого в Go часто используют gorilla/sessions. Проще, но серверу приходится всё помнить, ёбаный в рот!

И главное, блядь, в любом нормальном фреймворке (Gin, Echo, Chi) эту всю хуйню с проверкой засовывают в middleware. Чтобы не писать на каждом эндпоинте «эй, а ты кто?», а просто обернуть маршрут в защитный слой, и всё, пиздец. Красота!