본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout
TechProjectsBooksAbout
© 2026 Kreath. All rights reserved.
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 8장: 미들웨어, 인증, API 설계
2026년 8월 25일·프로그래밍·

8장: 미들웨어, 인증, API 설계

Go 백엔드의 미들웨어 패턴, JWT 인증 구현, CORS 설정, 레이트 리미팅, 요청 유효성 검사, OpenAPI/Swagger 문서 생성, log/slog를 활용한 구조화된 로깅을 다룹니다.

11분1,021자10개 섹션
concurrencyperformancedesign-patternstestinginfrastructure
공유
go-backend8 / 11
1234567891011
이전7장: 데이터베이스 연동 - SQL, ORM, 마이그레이션다음9장: 테스트 전략 - 단위, 통합, 벤치마크

학습 목표

  • 미들웨어 체이닝 패턴을 설계하고 구현할 수 있다
  • JWT 기반 인증 시스템을 구축할 수 있다
  • CORS, 레이트 리미팅 등 보안 미들웨어를 적용할 수 있다
  • 구조화된 로깅(slog)을 프로덕션 환경에 적합하게 설정할 수 있다

미들웨어 패턴

미들웨어의 구조

Go에서 미들웨어는 http.Handler를 받아 http.Handler를 반환하는 함수입니다. 이 패턴을 통해 요청 처리 전후에 공통 로직을 삽입할 수 있습니다.

middleware-pattern.go
go
// 미들웨어 타입 정의
type Middleware func(http.Handler) http.Handler
 
// 미들웨어 체이닝
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
    // 역순으로 적용하여 올바른 실행 순서 보장
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}
 
// 사용 예시
handler := Chain(
    finalHandler,
    LoggingMiddleware,
    AuthMiddleware,
    CORSMiddleware,
    RateLimitMiddleware,
)

요청 ID 미들웨어

모든 요청에 고유 식별자를 부여하여 분산 시스템에서의 추적을 가능하게 합니다.

request-id-middleware.go
go
func RequestIDMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        requestID := r.Header.Get("X-Request-ID")
        if requestID == "" {
            requestID = uuid.New().String()
        }
 
        ctx := context.WithValue(r.Context(), requestIDKey, requestID)
        w.Header().Set("X-Request-ID", requestID)
 
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

응답 래퍼

응답 상태 코드와 바이트 수를 캡처하려면 http.ResponseWriter를 래핑해야 합니다.

response-wrapper.go
go
type responseWriter struct {
    http.ResponseWriter
    statusCode int
    bytes      int
}
 
func newResponseWriter(w http.ResponseWriter) *responseWriter {
    return &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
}
 
func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}
 
func (rw *responseWriter) Write(b []byte) (int, error) {
    n, err := rw.ResponseWriter.Write(b)
    rw.bytes += n
    return n, err
}

JWT 인증

JWT 구조

JWT(JSON Web Token)는 세 부분으로 구성됩니다: 헤더(Header), 페이로드(Payload), 서명(Signature).

JWT 구조
text
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMjM0fQ.signature
|--- Header ---|.|----- Payload -----|.|- Signature -|

JWT 생성과 검증

jwt-service.go
go
import "github.com/golang-jwt/jwt/v5"
 
type Claims struct {
    UserID int64  `json:"user_id"`
    Email  string `json:"email"`
    Role   string `json:"role"`
    jwt.RegisteredClaims
}
 
type JWTService struct {
    secretKey     []byte
    accessExpiry  time.Duration
    refreshExpiry time.Duration
}
 
func NewJWTService(secret string) *JWTService {
    return &JWTService{
        secretKey:     []byte(secret),
        accessExpiry:  15 * time.Minute,
        refreshExpiry: 7 * 24 * time.Hour,
    }
}
 
// 액세스 토큰 생성
func (s *JWTService) GenerateAccessToken(userID int64, email, role string) (string, error) {
    claims := Claims{
        UserID: userID,
        Email:  email,
        Role:   role,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.accessExpiry)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "myapp",
        },
    }
 
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(s.secretKey)
}
 
// 토큰 검증
func (s *JWTService) ValidateToken(tokenStr string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenStr, &Claims{},
        func(token *jwt.Token) (interface{}, error) {
            if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, fmt.Errorf("예상하지 못한 서명 방식: %v", token.Header["alg"])
            }
            return s.secretKey, nil
        },
    )
    if err != nil {
        return nil, fmt.Errorf("토큰 파싱 실패: %w", err)
    }
 
    claims, ok := token.Claims.(*Claims)
    if !ok || !token.Valid {
        return nil, errors.New("유효하지 않은 토큰")
    }
 
    return claims, nil
}

인증 미들웨어

auth-middleware.go
go
func AuthMiddleware(jwtService *JWTService) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authHeader := r.Header.Get("Authorization")
            if authHeader == "" {
                writeError(w, http.StatusUnauthorized, "인증 토큰이 필요합니다")
                return
            }
 
            // "Bearer <token>" 형식 파싱
            parts := strings.SplitN(authHeader, " ", 2)
            if len(parts) != 2 || parts[0] != "Bearer" {
                writeError(w, http.StatusUnauthorized, "잘못된 인증 형식입니다")
                return
            }
 
            claims, err := jwtService.ValidateToken(parts[1])
            if err != nil {
                writeError(w, http.StatusUnauthorized, "유효하지 않은 토큰입니다")
                return
            }
 
            // 컨텍스트에 사용자 정보 저장
            ctx := context.WithValue(r.Context(), userClaimsKey, claims)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}
 
// 핸들러에서 사용자 정보 추출
func GetUserClaims(ctx context.Context) (*Claims, bool) {
    claims, ok := ctx.Value(userClaimsKey).(*Claims)
    return claims, ok
}
Warning

JWT 시크릿 키는 환경 변수로 관리하고, 코드에 하드코딩하지 마세요. 프로덕션에서는 RS256(비대칭 키) 알고리즘과 키 로테이션을 고려해야 합니다.


CORS 설정

CORS(Cross-Origin Resource Sharing)는 브라우저의 동일 출처 정책(Same-Origin Policy)을 우회하여 다른 도메인에서 API를 호출할 수 있게 합니다.

cors-middleware.go
go
func CORSMiddleware(allowedOrigins []string) Middleware {
    originSet := make(map[string]bool)
    for _, o := range allowedOrigins {
        originSet[o] = true
    }
 
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            origin := r.Header.Get("Origin")
            if originSet[origin] {
                w.Header().Set("Access-Control-Allow-Origin", origin)
            }
            w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
            w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
            w.Header().Set("Access-Control-Max-Age", "86400")
 
            // Preflight 요청 처리
            if r.Method == http.MethodOptions {
                w.WriteHeader(http.StatusNoContent)
                return
            }
 
            next.ServeHTTP(w, r)
        })
    }
}

요청 유효성 검사

go-playground/validator

validation.go
go
import "github.com/go-playground/validator/v10"
 
var validate = validator.New()
 
type CreateUserRequest struct {
    Name     string `json:"name" validate:"required,min=2,max=50"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=8,max=72"`
    Age      int    `json:"age" validate:"omitempty,gte=0,lte=150"`
}
 
func decodeAndValidate[T any](r *http.Request) (*T, error) {
    var req T
 
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        return nil, fmt.Errorf("요청 본문 파싱 실패: %w", err)
    }
 
    if err := validate.Struct(&req); err != nil {
        var validationErrors validator.ValidationErrors
        if errors.As(err, &validationErrors) {
            messages := make([]string, 0, len(validationErrors))
            for _, e := range validationErrors {
                messages = append(messages,
                    fmt.Sprintf("필드 '%s': %s 조건 미충족", e.Field(), e.Tag()))
            }
            return nil, &ValidationError{Fields: messages}
        }
        return nil, err
    }
 
    return &req, nil
}

구조화된 로깅 -- log/slog

slog 기본 사용법

Go 1.21에서 도입된 log/slog는 구조화된 로깅을 표준 라이브러리 수준에서 지원합니다.

slog-setup.go
go
import "log/slog"
 
func setupLogger(env string) *slog.Logger {
    var handler slog.Handler
 
    switch env {
    case "production":
        handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
            Level: slog.LevelInfo,
        })
    default:
        handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
            Level: slog.LevelDebug,
        })
    }
 
    logger := slog.New(handler)
    slog.SetDefault(logger)
    return logger
}

로깅 미들웨어

logging-middleware.go
go
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        rw := newResponseWriter(w)
 
        next.ServeHTTP(rw, r)
 
        duration := time.Since(start)
        requestID, _ := r.Context().Value(requestIDKey).(string)
 
        slog.Info("HTTP 요청 처리",
            "method", r.Method,
            "path", r.URL.Path,
            "status", rw.statusCode,
            "duration_ms", duration.Milliseconds(),
            "bytes", rw.bytes,
            "request_id", requestID,
            "remote_addr", r.RemoteAddr,
            "user_agent", r.UserAgent(),
        )
    })
}
JSON 로그 출력 예시
text
{
  "time": "2026-08-25T14:30:00.000Z",
  "level": "INFO",
  "msg": "HTTP 요청 처리",
  "method": "GET",
  "path": "/api/v1/users/42",
  "status": 200,
  "duration_ms": 12,
  "bytes": 256,
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Tip

프로덕션 환경에서는 JSON 형식의 구조화된 로그를 사용하세요. 이 형식은 Elasticsearch, Loki, CloudWatch 같은 로그 수집 시스템에서 파싱과 검색이 용이합니다.


OpenAPI/Swagger 문서 생성

swaggo/swag

swagger-annotations.go
go
// @Summary 사용자 조회
// @Description ID로 사용자 정보를 조회합니다
// @Tags users
// @Accept json
// @Produce json
// @Param id path int true "사용자 ID"
// @Success 200 {object} User
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Security BearerAuth
// @Router /api/v1/users/{id} [get]
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
    // 구현
}
Swagger 문서 생성
bash
# swag 도구 설치
go install github.com/swaggo/swag/cmd/swag@latest
 
# 문서 생성
swag init -g cmd/server/main.go -o docs/swagger

미들웨어 스택 조립

실전 프로젝트에서의 미들웨어 스택 조립 예시입니다.

middleware-stack.go
go
func setupRouter(cfg *Config, jwtSvc *JWTService) http.Handler {
    r := chi.NewRouter()
 
    // 글로벌 미들웨어 -- 모든 요청에 적용
    r.Use(RequestIDMiddleware)
    r.Use(LoggingMiddleware)
    r.Use(RecoveryMiddleware)
    r.Use(CORSMiddleware(cfg.AllowedOrigins))
 
    // 공개 라우트
    r.Group(func(r chi.Router) {
        r.Post("/api/v1/auth/login", loginHandler)
        r.Post("/api/v1/auth/register", registerHandler)
        r.Get("/api/v1/health", healthHandler)
    })
 
    // 인증 필요 라우트
    r.Group(func(r chi.Router) {
        r.Use(AuthMiddleware(jwtSvc))
        r.Use(RateLimitMiddleware(100, time.Minute)) // 분당 100회
 
        r.Route("/api/v1/users", func(r chi.Router) {
            r.Get("/", listUsers)
            r.Get("/{id}", getUser)
            r.Put("/{id}", updateUser)
        })
 
        // 관리자 전용
        r.Group(func(r chi.Router) {
            r.Use(RequireRole("admin"))
            r.Delete("/api/v1/users/{id}", deleteUser)
        })
    })
 
    return r
}

정리

이번 장에서 살펴본 핵심 내용을 정리합니다.

  • 미들웨어는 http.Handler를 래핑하는 패턴으로, 체이닝을 통해 요청 처리 파이프라인을 구성합니다
  • JWT 인증은 액세스 토큰과 리프레시 토큰의 이중 구조로 구현하며, 미들웨어에서 검증합니다
  • CORS는 프리플라이트 요청 처리를 포함하여 올바르게 설정해야 합니다
  • log/slog는 Go 1.21+ 표준 라이브러리로, JSON 형식의 구조화된 로깅을 지원합니다
  • 미들웨어 스택은 공개/인증/관리자 계층으로 분리하여 조립합니다

다음 장 미리보기

9장에서는 테스트 전략을 다룹니다. testing 패키지의 테이블 기반 테스트, testify, httptest를 활용한 HTTP 핸들러 테스트, 목(Mock) 생성, testcontainers-go를 활용한 통합 테스트, 벤치마크, 퍼징 테스트를 살펴봅니다.

이 글이 도움이 되셨나요?

관련 글

프로그래밍

7장: 데이터베이스 연동 - SQL, ORM, 마이그레이션

Go에서 데이터베이스를 다루는 방법을 database/sql, sqlx, GORM, pgx를 통해 비교하고, 커넥션 풀링, 트랜잭션, 마이그레이션, 쿼리 빌더 패턴을 실전 예제로 다룹니다.

2026년 8월 23일·14분
프로그래밍

9장: 테스트 전략 - 단위, 통합, 벤치마크

Go의 testing 패키지를 중심으로 테이블 기반 테스트, testify, httptest, 목(Mock) 생성, testcontainers-go 통합 테스트, 벤치마크, 퍼징 테스트 전략을 다룹니다.

2026년 8월 28일·14분
프로그래밍

10장: gRPC와 마이크로서비스 통신

Protocol Buffers 정의부터 Go에서의 gRPC 서버/클라이언트 구현, 유니어리/스트리밍 통신, 인터셉터, gRPC-Gateway를 통한 REST 변환, 헬스 체킹과 서비스 디스커버리를 다룹니다.

2026년 8월 30일·12분
이전 글7장: 데이터베이스 연동 - SQL, ORM, 마이그레이션
다음 글9장: 테스트 전략 - 단위, 통합, 벤치마크

댓글

목차

약 11분 남음
  • 학습 목표
  • 미들웨어 패턴
    • 미들웨어의 구조
    • 요청 ID 미들웨어
    • 응답 래퍼
  • JWT 인증
    • JWT 구조
    • JWT 생성과 검증
    • 인증 미들웨어
  • CORS 설정
  • 요청 유효성 검사
    • go-playground/validator
  • 구조화된 로깅 -- log/slog
    • slog 기본 사용법
    • 로깅 미들웨어
  • OpenAPI/Swagger 문서 생성
    • swaggo/swag
  • 미들웨어 스택 조립
  • 정리
  • 다음 장 미리보기