Создаем безопасный API на Golang используя Middleware

Posted by

Аутентификация — критически важный компонент безопасности современных веб-приложений. Она позволяет приложениям проверять личность пользователей, получающих доступ к защищенным ресурсам.

В этой статье мы рассмотрим, как создать безопасный API аутентификации на языке Go (Golang), используя лучший подход — промежуточное программное обеспечение (middleware).

Промежуточное программное обеспечение позволит нам проверять, аутентифицирован ли пользователь для доступа к определенным маршрутам в нашем приложении.

Примечание: прежде чем мы начнем, убедитесь, что у вас установлен Go на вашем компьютере, вам также понадобится редактор кода (я рекомендую vscode).

Создание структуры проекта

Начните с создания структуры проекта. Для этого примера мы создадим каталог с именем «authentication-api» и организуем наш код следующим образом:

  • cmd/main.go: Точка входа в приложение.
  • handlers/auth_handlers.go: Контроллеры аутентификации.
  • middleware/auth_middleware.go: Промежуточное программное обеспечение аутентификации.
  • models/user.go: Определение модели пользователя.
  • utils/jwt: Утилиты для работы с JSON Web Tokens (JWT).

Реализация модели пользователя

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

Простая модель пользователя может быть определена следующим образом:

package models

type User struct {
    ID       uint   `json:"id"`
    Username string `json:"username"`
    Password string `json:"-"`
}

В этом примере мы храним идентификатор, имя пользователя и пароль пользователя.

Помните, что хранение паролей в открытом виде не является безопасной практикой в продакшн-системах.

Для реальной системы аутентификации следует использовать методы хэширования паролей.

Генерация JWT токенов

Теперь давайте создадим утилитарные функции для работы с токенами JWT.

Мы будем использовать библиотеку github.com/dgrijalva/jwt-go для генерации и проверки токенов JWT.

package utils

import (
    "time"
    "github.com/dgrijalva/jwt-go"
)

var secretKey = []byte("secretpassword")

// GenerateToken generates a JWT token with the user ID as part of the claims
func GenerateToken(userID uint) (string, error) {
    claims := jwt.MapClaims{}
    claims["user_id"] = userID
    claims["exp"] = time.Now().Add(time.Hour * 1).Unix() // Token valid for 1 hour

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

// VerifyToken verifies a token JWT validate 
func VerifyToken(tokenString string) (jwt.MapClaims, error) {
    // Parse the token
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        // Check the signing method
        if _, ok := token.Method.(*jwt.SigningMethodHS256); !ok {
            return nil, fmt.Errorf("Invalid signing method")
        }

        return secretKey, nil
    })

    // Check for errors
    if err != nil {
        return nil, err
    }

    // Validate the token
    if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
        return claims, nil
    }

    return nil, fmt.Errorf("Invalid token")
}

В этом примере мы используем простой секретный ключ для подписи токенов JWT. В реальной производственной среде следует использовать более безопасный секретный ключ и хранить его в безопасном месте.

Создаем middleware аутентификации


Далее давайте создадим middleware аутентификации. Он будет использоваться для защиты маршрутов, требующих аутентификации.

В этом примере мы используем фреймворк Gin для создания маршрутов и обработки наших HTTP-запросов.

package middleware

import (
    "net/http"
    "strings"
    "github.com/gin-gonic/gin"
    "authentication-api/utils"
)

// AuthenticationMiddleware checks if the user has a valid JWT token
func AuthenticationMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        if tokenString == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing authentication token"})
            c.Abort()
            return
        }

        // The token should be prefixed with "Bearer "
        tokenParts := strings.Split(tokenString, " ")
        if len(tokenParts) != 2 || tokenParts[0] != "Bearer" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authentication token"})
            c.Abort()
            return
        }

        tokenString = tokenParts[1]

        claims, err := utils.VerifyToken(tokenString)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authentication token"})
            c.Abort()
            return
        }

        c.Set("user_id", claims["user_id"])
        c.Next()
    }
}


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

Если токен действителен, идентификатор пользователя сохраняется в контексте Gin для дальнейшего использования.

Создаем handlers для входа и регистрации

Мы создадим две функции: одну для входа в систему и другую для регистрации нового пользователя.

package handlers

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "authentication-api/models"
    "authentication-api/utils"
)

// Function for logging in
func Login(c *gin.Context) {
    var user models.User

    // Check user credentials and generate a JWT token
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid data"})
        return
    }

    // Check if credentials are valid (replace this logic with real authentication)
    if user.Username == "user" && user.Password == "password" {
        // Generate a JWT token
        token, err := utils.GenerateToken(user.ID)
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Error generating token"})
            return
        }

        c.JSON(http.StatusOK, gin.H{"token": token})
    } else {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
    }
}

// Function for registering a new user (for demonstration purposes)
func Register(c *gin.Context) {
    var user models.User

    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid data"})
        return
    }

    // Remember to securely hash passwords before storing them
    user.ID = 1 // Just for demonstration purposes
    c.JSON(http.StatusCreated, gin.H{"message": "User registered successfully"})
}

Функция Login проверяет учетные данные пользователя и генерирует токен JWT, если учетные данные действительны.

Функция Register предназначена исключительно для демонстрационных целей и не должна использоваться в проде.

Конфигурация роутов и старт сервера

Наконец, мы настроим маршруты и запустим сервер в файле main.go:

package main

import (
    "github.com/gin-gonic/gin"
    "authentication-api/handlers"
    "authentication-api/middleware"
)

func main() {
    r := gin.Default()

    // Public routes (do not require authentication)
    publicRoutes := r.Group("/public")
    {
        publicRoutes.POST("/login", handlers.Login)
        publicRoutes.POST("/register", handlers.Register)
    }

    // Protected routes (require authentication)
    protectedRoutes := r.Group("/protected")
    protectedRoutes.Use(middleware.AuthenticationMiddleware())
    {
        // Protected routes here
    }

    r.Run(":8080")
}

В этом примере мы создали две группы маршрутов:

  • publicRoutes: для открытых маршрутов, которым не требуется аутентификация.
  • protectedRoutes: для защищенных маршрутов, которым требуется аутентификация.

Мы используем наше промежуточное программное обеспечение аутентификации (AuthenticationMiddleware) для защиты маршрутов, которые должны находиться внутри группы маршрутов protectedRoutes.

Заключение

В этой статье вы узнали, как создать безопасный API аутентификации на языке Golang и реализовать middleware для проверки аутентификации пользователей на определенных маршрутах.

Помните, что это всего лишь простой пример. В производственной среде вам следует применять более строгие практики безопасности, такие как безопасное хранение паролей и правильное управление секретными ключами.

Убедитесь, что вы адаптируете этот пример под конкретные потребности вашего приложения и следуете рекомендованным практикам безопасности.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *