[Golang] JWT Authentication in Gin Web Framework

소개

이 글에서는 Golang의 Gin 웹 프레임워크를 사용하여 JWT(JSON Web Token) 기반의 인증 시스템을 구현하는 방법을 알아보겠습니다.

필요한 패키지

go get -u github.com/gin-gonic/gin
go get -u github.com/golang-jwt/jwt/v5
go get -u github.com/joho/godotenv

프로젝트 구조

├── .env
├── main.go
├── middleware
│   └── auth.go
├── models
│   └── user.go
└── handlers
    └── auth.go

환경 변수 설정

.env 파일을 생성하고 JWT 시크릿 키를 설정합니다:

JWT_SECRET=your-secret-key

사용자 모델 정의

models/user.go:

package models

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

JWT 미들웨어 구현

middleware/auth.go:

package middleware

import (
    "net/http"
    "os"
    "strings"

    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
)

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is required"})
            c.Abort()
            return
        }

        bearerToken := strings.Split(authHeader, " ")
        if len(bearerToken) != 2 {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token format"})
            c.Abort()
            return
        }

        tokenString := bearerToken[1]
        token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
            return []byte(os.Getenv("JWT_SECRET")), nil
        })

        if err != nil || !token.Valid {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
            c.Abort()
            return
        }

        claims, ok := token.Claims.(jwt.MapClaims)
        if !ok {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"})
            c.Abort()
            return
        }

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

인증 핸들러 구현

handlers/auth.go:

package handlers

import (
    "net/http"
    "os"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
    "your-project/models"
)

func Login(c *gin.Context) {
    var user models.User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // 여기에서 실제 데이터베이스 검증 로직을 구현해야 합니다
    // 예시를 위해 하드코딩된 검증을 사용합니다
    if user.Username != "test" || user.Password != "password" {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
        return
    }

    // JWT 토큰 생성
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        "user_id": 1,
        "exp":     time.Now().Add(time.Hour * 24).Unix(),
    })

    tokenString, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not generate token"})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "token": tokenString,
    })
}

func Protected(c *gin.Context) {
    userID, exists := c.Get("user_id")
    if !exists {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "message": "This is a protected route",
        "user_id": userID,
    })
}

메인 애플리케이션 구현

main.go:

package main

import (
    "log"

    "github.com/gin-gonic/gin"
    "github.com/joho/godotenv"
    "your-project/handlers"
    "your-project/middleware"
)

func main() {
    if err := godotenv.Load(); err != nil {
        log.Fatal("Error loading .env file")
    }

    r := gin.Default()

    // 공개 라우트
    r.POST("/login", handlers.Login)

    // 보호된 라우트
    protected := r.Group("/api")
    protected.Use(middleware.AuthMiddleware())
    {
        protected.GET("/protected", handlers.Protected)
    }

    r.Run(":8080")
}

사용 방법

로그인

curl -X POST http://localhost:8080/login \
-H "Content-Type: application/json" \
-d '{"username": "test", "password": "password"}'

보호된 엔드포인트 접근

curl http://localhost:8080/api/protected \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

보안 고려사항

  1. 실제 프로덕션 환경에서는 안전한 비밀번호 해싱을 구현해야 합니다.
  2. 토큰 만료 시간을 적절히 설정해야 합니다.
  3. HTTPS를 사용하여 통신을 암호화해야 합니다.
  4. 토큰 블랙리스트 구현을 고려해야 합니다.

결론

이 예제에서는 Gin 프레임워크와 JWT를 사용하여 기본적인 인증 시스템을 구현해보았습니다. 실제 프로덕션 환경에서는 추가적인 보안 조치와 에러 처리가 필요합니다.