체인의정석

Go) JWT 발급 및 인증 로직 (백엔드) 본문

개발/backend(go)

Go) JWT 발급 및 인증 로직 (백엔드)

체인의정석 2025. 4. 10. 15:57
728x90

서명 로직 이후에 지갑 서명이 확인되면 해당 지갑의 role을 읽어와서 JWT를 발급해주는 구조

지갑 서명이라는 검증 로직이 있기 때문에 Refresh Token은 생략

*핵심 로직만 함수화 시켰으며, 함수명 및 변수들은 모두 임의로 지정

기능구현

JWT 발급 함수 (발급 해주고 리턴)

import (
	"github.com/golang-jwt/jwt/v5"
	"time"
)

func GenerateJWT(secret string, userID string, role string, issuer string) (string, error) {
	claims := jwt.MapClaims{
		"user_id": userID,                       // 사용자 식별자
		"role":    role,                         // 권한: "viewer", "editor", "admin"
		"exp":     time.Now().Add(24 * time.Hour).Unix(), // 만료 시간
		"iat":     time.Now().Unix(),            // 발급 시간
		"iss":     issuer,                       // 발급자
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(secret))
}

JWT 검증함수 (JWT 받아와서 유효성 검증)

import (
	"fmt"
	"github.com/golang-jwt/jwt/v5"
)

func ParseJWT(tokenStr string, secret string) (jwt.MapClaims, error) {
	token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method")
		}
		return []byte(secret), nil
	})

	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		return claims, nil
	}
	return nil, fmt.Errorf("invalid token")
}

Role 기반으로 JWT를 받아와서 각각의 Role이 적합한지 판단해주는 미들웨어 (인증 함수를 사용하여 JWT에 기록된 정보별로 유효성을 판단해주는 역할) Router 경로에 middleware로 따로 분류

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

func AuthMiddleware(secret string, allowedRoles ...string) gin.HandlerFunc {
	return func(c *gin.Context) {
		authHeader := c.GetHeader("Authorization")
		if !strings.HasPrefix(authHeader, "Bearer ") {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Missing or invalid Authorization header"})
			return
		}

		tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
		claims, err := ParseJWT(tokenStr, secret)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
			return
		}

		role, ok := claims["role"].(string)
		if !ok {
			c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "Role not found in token"})
			return
		}

		for _, r := range allowedRoles {
			if role == r {
				c.Set("userClaims", claims)
				c.Next()
				return
			}
		}

		c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
	}
}

API 호출 시 JWT를 검사해야 하기 때문에 해당 부분은 다음과 같이 사용, API 호출 시 입력된 롤을 받아와서 해당 롤이 JWT의 롤이랑 일치하는지 체크한다. API 별로 권한 체계가 다 다르다면 이에 맞게 여기서 다 지정해주면 된다.

account.GET("get_something", AuthRoleMiddleware(p.config, "role1", "role2", "role3"), ExampleController.GetSomeThing)

 

Swagger 테스트

JWT인증을 받은 후의 API 처리의 경우 Swagger에서 테스트를 하려면 아래와 같이 주석을 추가해 주어야 한다.

main.go 또는 router.go 상단:

// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization

각 API 주석에 추가:

// @Security BearerAuth

위의 작업이 완료되면 아래와 같이 Authorize가 뜨게 되는데

리턴받은 JWT토큰이 있다면 Authorize 버튼을 클릭하고 아래처럼 입력한다:

Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

※ JWT토큰 앞에 Bearer 를 붙여야 정상 작동한다.

 

728x90
반응형
Comments