Аутентификация — критически важный компонент безопасности современных веб-приложений. Она позволяет приложениям проверять личность пользователей, получающих доступ к защищенным ресурсам.
В этой статье мы рассмотрим, как создать безопасный 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 для проверки аутентификации пользователей на определенных маршрутах.
Помните, что это всего лишь простой пример. В производственной среде вам следует применять более строгие практики безопасности, такие как безопасное хранение паролей и правильное управление секретными ключами.
Убедитесь, что вы адаптируете этот пример под конкретные потребности вашего приложения и следуете рекомендованным практикам безопасности.
Добавить комментарий