Введение в OAuth 2.0 и JWT на Go
Современные веб-приложения и API требуют надежной и безопасной системы аутентификации и авторизации. Два наиболее популярных стандарта в этой области — OAuth 2.0 и JWT (JSON Web Tokens). На языке Go (Golang) реализация этих механизмов становится особенно эффективной благодаря встроенной поддержке конкурентности, высокой производительности и богатой экосистеме библиотек.
В этой статье мы подробно разберем, как реализовать OAuth 2.0 и JWT авторизацию на Go. Вы узнаете, как настроить сервер, генерировать и проверять токены, а также защитить свои эндпоинты. Материал будет полезен как начинающим, так и опытным Go-разработчикам.
Основы OAuth 2.0 и JWT
Что такое OAuth 2.0?
OAuth 2.0 — это протокол авторизации, который позволяет приложениям получать ограниченный доступ к ресурсам пользователя без передачи пароля. Вместо этого используются токены доступа (access tokens). Основные участники процесса:
- Resource Owner — пользователь, владеющий данными
- Client — приложение, запрашивающее доступ
- Authorization Server — сервер, выдающий токены
- Resource Server — сервер, предоставляющий защищенные ресурсы
Что такое JWT?
JWT (JSON Web Token) — это компактный и самодостаточный формат для передачи данных между сторонами в виде JSON-объекта. Токен состоит из трех частей, разделенных точками:
header.payload.signature
Преимущества JWT:
- Не требует хранения сессий на сервере (stateless)
- Содержит всю необходимую информацию (claims)
- Подписан для проверки целостности
Реализация JWT авторизации на Go
Для работы с JWT в Go мы будем использовать популярную библиотеку golang-jwt/jwt/v5. Установите ее командой:
go get github.com/golang-jwt/jwt/v5
Генерация JWT токена
Создадим функцию для генерации access и refresh токенов:
package auth
import (
"time"
"github.com/golang-jwt/jwt/v5"
)
var jwtSecret = []byte("your-secret-key")
type Claims struct {
UserID uint `json:"user_id"`
Email string `json:"email"`
jwt.RegisteredClaims
}
func GenerateAccessToken(userID uint, email string) (string, error) {
claims := &Claims{
UserID: userID,
Email: email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "myapp",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
func GenerateRefreshToken(userID uint) (string, error) {
claims := &Claims{
UserID: userID,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
Проверка JWT токена
Middleware для проверки токена в HTTP-запросах:
package middleware
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"myapp/auth"
)
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing authorization header"})
c.Abort()
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
if tokenString == authHeader {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token format"})
c.Abort()
return
}
claims, err := auth.ValidateToken(tokenString)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid or expired token"})
c.Abort()
return
}
c.Set("userID", claims.UserID)
c.Set("email", claims.Email)
c.Next()
}
}
func ValidateToken(tokenString string) (*auth.Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &auth.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.(*auth.Claims)
if !ok || !token.Valid {
return nil, jwt.ErrTokenInvalid
}
return claims, nil
}
Интеграция с OAuth 2.0
Теперь реализуем полный поток OAuth 2.0 с использованием стороннего провайдера (например, Google). Для этого будем использовать библиотеку golang.org/x/oauth2.
Настройка OAuth 2.0 клиента
package oauth
import (
"encoding/json"
"net/http"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
var googleOauthConfig = &oauth2.Config{
RedirectURL: "http://localhost:8080/auth/google/callback",
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"},
Endpoint: google.Endpoint,
}
func HandleGoogleLogin(w http.ResponseWriter, r *http.Request) {
url := googleOauthConfig.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func HandleGoogleCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
token, err := googleOauthConfig.Exchange(r.Context(), code)
if err != nil {
http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
return
}
client := googleOauthConfig.Client(r.Context(), token)
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
http.Error(w, "Failed to get user info", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
var userInfo map[string]interface{}
json.NewDecoder(resp.Body).Decode(&userInfo)
// Генерируем наш JWT на основе полученных данных