Введение: зачем нужны OAuth 2.0 и JWT в Go?
Современная веб-разработка немыслима без безопасной аутентификации и авторизации. Если вы пишете на Go (Golang) свой первый API, микросервис или веб-приложение, рано или поздно вы столкнетесь с необходимостью защитить данные пользователей. Два стандарта — OAuth 2.0 и JWT (JSON Web Token) — стали золотым стандартом в этой области.
В этой статье мы разберем, как работают OAuth 2.0 и JWT, и напишем простую, но рабочую реализацию на Go. Вы узнаете, как выдавать токены доступа, проверять их и защищать свои эндпоинты. Статья ориентирована на начинающих разработчиков, знакомых с основами Go и HTTP.
Что такое OAuth 2.0 и JWT? Простыми словами
OAuth 2.0 — протокол делегированного доступа
OAuth 2.0 — это не библиотека и не функция, а протокол. Представьте, что вы хотите войти на сайт через Google. OAuth 2.0 позволяет сайту получить от Google ограниченный доступ к вашим данным (например, email) без того, чтобы вы передавали ему свой пароль от Google. Это называется "делегированный доступ".
Основные роли в OAuth 2.0:
- Resource Owner — пользователь (вы).
- Client — приложение, которое хочет получить доступ (наш сайт).
- Authorization Server — сервер, который выдает токены (Google).
- Resource Server — сервер с данными пользователя (API Google).
JWT — формат токена
JWT (JSON Web Token) — это компактный и самодостаточный способ передачи информации между сторонами в виде JSON-объекта. Он состоит из трех частей, разделенных точками:
- Header — тип токена и алгоритм подписи (например, HS256).
- Payload — данные (claims): ID пользователя, роль, срок действия.
- Signature — подпись, которая гарантирует, что токен не был изменен.
JWT часто используется как Access Token в OAuth 2.0. Сервер подписывает токен секретным ключом, а клиент отправляет его в заголовке Authorization: Bearer <token>.
Практика: реализация JWT авторизации на Go
Мы напишем небольшой HTTP-сервер на Go, который будет выдавать JWT токены и проверять их на защищенных маршрутах. Для работы с JWT мы используем популярную библиотеку github.com/golang-jwt/jwt/v5.
Шаг 1: Установка зависимостей и структура проекта
Создайте новый модуль и установите библиотеку:
go mod init auth-example
go get github.com/golang-jwt/jwt/v5
go get golang.org/x/crypto
Структура файлов будет простой:
main.go— точка входа и маршруты.auth.go— логика создания и проверки JWT.handlers.go— обработчики HTTP.
Шаг 2: Создание JWT токена
Создадим файл auth.go. Здесь мы определим секретный ключ (в реальном проекте храните его в .env) и функцию для генерации токена.
package main
import (
"time"
"github.com/golang-jwt/jwt/v5"
)
var jwtSecret = []byte("your-secret-key-here")
type Claims struct {
UserID int `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
func GenerateToken(userID int, username string) (string, error) {
claims := Claims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "auth-example",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
Шаг 3: Проверка и парсинг JWT токена
Добавим в auth.go функцию для валидации токена из HTTP-запроса:
func ValidateToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid
}
return jwtSecret, nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, jwt.ErrTokenInvalid
}
return claims, nil
}
Шаг 4: HTTP обработчики и middleware
В handlers.go создадим простой обработчик входа (логин) и защищенный маршрут. Также напишем middleware для проверки JWT.
package main
import (
"encoding/json"
"net/http"
"strings"
)
// Структура для ответа
type TokenResponse struct {
Token string `json:"token"`
}
// Обработчик логина (имитация)
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// В реальном проекте здесь проверка логина/пароля из БД
// Сейчас просто генерируем токен для пользователя с ID=1
token, err := GenerateToken(1, "john_doe")
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(TokenResponse{Token: token})
}
// Middleware для проверки JWT
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Missing authorization header", http.StatusUnauthorized)
return
}
// Ожидаем формат "Bearer <token>"
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts