jwt
JWT는 JSON 포맷을 사용하여 정보를 안전하게 전달하기 위한 토큰 기반 인증 방식입니다. 서버와 클라이언트 간의 인증과 권한 부여에 널리 사용됩니다. 이 글은 JWT의 구조, 사용 방식과 함께 Payload 및 Claim의 구성 요소, 그리고 실제 Go 언어에서의 JWT 생성 및 검증 예시까지 종합적으로 설명합니다. 실제 서비스에서 토큰 기반 인증 시스템을 설계할 때 기준 문서로 활용할 수 있습니다.
JWT의 구성 요소
JWT는 점(.)으로 구분된 3개의 부분으로 이루어져 있습니다:
<Header>.<Payload>.<Signature>
- Header: 토큰의 타입(JWT)과 알고리즘(HS256 등) 명시
- Payload: 실제 데이터(Claim) 포함 - 사용자 정보, 권한 등
- Signature: 위조 방지를 위한 서명.
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
JWT 사용 시나리오
- 클라이언트가 로그인 정보를 서버에 보냄
- 서버는 로그인 정보를 확인 후 JWT를 생성하여 응답
- 클라이언트는 JWT를 저장 (주로 로컬 스토리지, 쿠키 등)
- 이후 요청 시 Authorization 헤더에 JWT 포함하거나 쿠키에 포함하여 요청
- 서버는 JWT의 유효성 검사 후 요청 처리
JWT는 상태를 저장하지 않기 때문에 서버가 세션을 따로 유지하지 않아도 됩니다 (Stateless).
JWT Payload와 Claim 상세 정리
Payload
JWT의 Payload는 토큰의 실제 정보를 담고 있는 부분으로, 여러 개의 Claim(클레임)으로 구성됩니다. 클레임은 “누가, 언제, 어떤 권한으로” 등의 정보를 포함하며, 토큰의 주요 기능을 담당합니다.
Claim의 종류
Claim은 크게 다음 세 가지로 나뉩니다:
1. Registered Claims (등록된 클레임)
JWT 표준 RFC 7519에서 정의한 권장 클레임들이며, 선택적으로 사용됩니다.
iss
(Issuer) 토큰 발급자. 예:"myserver.com"
sub
(Subject) 토큰의 주제 또는 사용자 식별자. 예:"user123"
aud
(Audience) 토큰의 대상자(수신자). 예:"myapp.com"
exp
(Expiration Time) 토큰의 만료 시간. UNIX timestamp 형식 사용. 예:1710000000
nbf
(Not Before) 토큰이 유효해지기 시작하는 시간. 이 시간 이전에는 토큰이 무효. 예:1709990000
iat
(Issued At) 토큰이 발급된 시간. 일반적으로 로그인 시각과 같음. 예:1709980000
jti
(JWT ID) 토큰의 고유 식별자. 중복 방지나 토큰 무효화 관리 시 사용. 예:"abc-123-xyz"
시간 관련 클레임들은 모두 UNIX timestamp 사용 권장
2. Public Claims (공개 클레임)
- 표준에는 없지만 공개적으로 사용될 수 있는 클레임
- 충돌 방지를 위해 URI 네임스페이스 형태 권장
{
"https://example.com/roles": ["admin", "editor"]
}
3. Private Claims (비공개 클레임)
- 애플리케이션 간에 자유롭게 정의하는 사용자 맞춤 클레임
- 예:
user_id
,role
,permissions
,team_id
등
{
"user_id": "abc123",
"role": "admin",
"permissions": ["read", "write", "delete"]
}
민감한 정보(예: 비밀번호)는 넣지 말 것 — Payload는 암호화되지 않음
예시 Payload
{
"iss": "myapi.example.com",
"sub": "user123",
"aud": "mobileapp",
"exp": 1711672800,
"iat": 1711586400,
"role": "admin",
"permissions": ["read", "write", "delete"]
}
Claim 설계
exp
는 반드시 포함- 토큰 유효기간을 명시하여 자동 로그아웃 및 갱신 정책 수립에 활용
sub
에는 유저 고유 ID나 이메일을 넣기- 사용자를 식별할 수 있는 정보로 접근 제어나 로그 기록 분석에 유리
- 권한은
role
,permissions
등으로 구체화- 역할 기반 접근 제어(RBAC), 세분화된 기능 제한 등을 구현할 수 있음
- 민감한 데이터는 절대 포함x
- Payload는 암호화되지 않으므로 비밀번호, 카드번호, 주민번호 등은 포함 x
- 서버 간 토큰 교환 시 최소한의 정보만 포함
- 내부 API 호출에서는 경량화된 토큰 설계로 성능 확보
- 클레임은 일관된 명명 규칙과 포맷을 유지
- 예:
snake_case
또는camelCase
중 하나로 통일
- 예:
- 클라이언트에 노출되는 값은 항상 검증 및 최소화
- 클라이언트에서 토큰을 디코딩할 수 있기 때문에 불필요한 정보는 제외
jti
를 사용해 토큰 무효화 처리 가능- 로그아웃 시 jti 블랙리스트 관리로 유효한 토큰 무력화 가능
Go에서 JWT 사용 예시
Go에서는 보통 github.com/golang-jwt/jwt/v5
패키지를 사용
generateJWT 함수 (JWT 생성)
import (
"time"
"github.com/golang-jwt/jwt/v5"
)
var jwtSecret = []byte("your-secret-key")
type CustomClaims struct {
UserID string `json:"user_id"`
Role string `json:"role"`
jwt.RegisteredClaims
}
func generateJWT(userID, role string) (string, error) {
claims := CustomClaims{
UserID: userID,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "myapi.example.com",
Subject: userID,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 1)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
validateJWT 함수 (JWT 유효성 검사)
func validateJWT(tokenString string) (*CustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err != nil || !token.Valid {
return nil, err
}
claims, ok := token.Claims.(*CustomClaims)
if !ok {
return nil, jwt.ErrInvalidKeyType
}
return claims, nil
}