JWT 소개
JWT(JSON Web Token)는 웹에서의 간결하고 자기 포함 방식의 인증 방법입니다. 헤더, 페이로드 및 시그니처 세 부분으로 구성됩니다. 헤더에는 토큰의 유형 및 암호화 알고리즘 정보, 페이로드에는 전달될 데이터(일반적으로 일부 권한 정보 및 사용자 식별 정보를 포함)가 포함되어 있으며 시그니처는 토큰의 무결성과 유효성을 확인하는 데 사용됩니다.
JWT는 주로 두 가지 시나리오에서 사용됩니다:
- 인증: 사용자가 로그인한 후 각 후속 요청에는 JWT가 포함되어 있어 사용자가 허용된 경로, 서비스 및 리소스에 액세스할 수 있습니다.
- 정보 교환: JWT는 디지털로 서명될 수 있으므로 당사자 간에 안전하게 정보를 전송하는 데 좋은 방법입니다. 예를 들어 공개/개인 키 쌍을 사용하여 디지털로 서명할 수 있습니다.
JWT 데이터 형식
JWT는 당사자 간에 교환될 정보를 간결하고 URL 안전하게 나타내는 방법입니다. JWT는 사실상 헤더.페이로드.시그니처
로 구성된 세 부분으로 나뉩니다. 다음으로, 이 세 부분의 데이터 형식을 자세히 설명하겠습니다.
1. 헤더
헤더는 일반적으로 두 부분으로 구성됩니다: 토큰 유형(일반적으로 JWT
) 및 사용된 서명 또는 암호화 알고리즘(예: HMAC SHA256
또는 RSA
)입니다. 헤더는 JSON으로 표시한 다음 Base64Url로 인코딩됩니다. 예를 들면:
{
"alg": "HS256",
"typ": "JWT"
}
인코딩 후에는 다음과 유사한 문자열을 얻을 수 있습니다:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2. 페이로드
페이로드에는 일련의 클레임이 포함되어 있으며, 이는 주체(일반적으로 사용자)와 기타 데이터에 대한 정보입니다. 페이로드에는 여러 사전 정의된 클레임(등록된 클레임이라고도 함) 및 사용자 정의 클레임(개인 클레임)이 포함될 수 있습니다.
사전 정의된 클레임은 다음과 같습니다:
-
iss
(Issuer): 발급자 -
exp
(Expiration Time): 만료 시간 -
sub
(Subject): 주제 -
aud
(Audience): 청중 -
iat
(Issued At): 발급 시간 -
nbf
(Not Before): 유효 시간 -
jti
(JWT ID): JWT의 고유 식별자
예를 들어, 페이로드는 다음과 같이 보일 수 있습니다 (JSON 형식):
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
이 정보는 또한 Base64Url로 인코딩되며, 다음과 유사한 문자열을 얻을 수 있습니다:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
3. 시그니처
시그니처 섹션은 앞에서 언급한 두 인코딩된 문자열을 서명하여 전송 중에 메시지가 변경되지 않았음을 확인하는 데 사용됩니다. 먼저 HMAC SHA256 알고리즘을 사용하는 경우 키를 지정하고, 헤더에 지정된 알고리즘을 사용하여 헤더와 페이로드를 서명해야 합니다.
예를 들어, 다음과 같은 헤더 및 페이로드가 있는 경우:
HeaderEncoded.HeaderPayload
키를 사용하여 서명하기 위한 의사 코드는 다음과 같을 수 있습니다:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secretKey)
그 결과로 생성된 서명된 문자열은 다음과 같을 수 있습니다:
dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY
완전한 JWT
이 세 부분을 점(.)으로 구분하여 결합하면 완전한 JWT가 형성됩니다:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY
JWT 표준은 매우 유연하며 페이로드에 필요한 모든 정보를 저장할 수 있습니다 (JWT 크기를 줄이고 보안상의 이유로 민감한 정보는 저장하면 안 됨). 또한 시그니처를 통해 이 정보의 무결성을 보장합니다.
Go JWT 라이브러리 설치하기
Go 커뮤니티에서는 JWT 처리를 위한 github.com/golang-jwt/jwt
패키지를 제공합니다. 이 라이브러리를 설치하는 것은 매우 간단합니다. 프로젝트 디렉토리에서 다음 명령을 실행하면 됩니다:
go get -u github.com/golang-jwt/jwt/v5
설치가 완료되면 다음과 같이 import
구문에 포함시킬 수 있습니다:
import "github.com/golang-jwt/jwt/v5"
간단한 토큰 생성하기
Go 언어와 HS256 알고리즘을 사용하여 간단한 JWT 토큰을 생성하려면 다음 단계를 따르셔야 합니다:
먼저, HS256 알고리즘을 사용하여 새 Token 객체를 생성합니다:
token := jwt.New(jwt.SigningMethodHS256)
다음으로, SignedString
메서드를 사용하여 이 토큰의 문자열 표현을 생성합니다. 이때 사용할 키를 전달합니다.
var mySigningKey = []byte("your-256-bit-secret")
strToken, err := token.SignedString(mySigningKey)
if err != nil {
log.Fatalf("오류 발생: %v", err)
}
fmt.Println(strToken)
이렇게 하면 어떠한 클레임도 포함되지 않은 간단한 토큰이 생성됩니다.
매개변수를 포함하는 토큰 생성하기
JWT의 주요 기능 중 하나는 정보를 전달하는 것입니다. 이러한 정보는 클레임을 통해 토큰에 인코딩됩니다. 예를 들어 사용자 정의 클레임을 만들어보겠습니다:
// 사용자 정의 클레임 구조체
type MyClaims struct {
jwt.RegisteredClaims
Username string `json:"username"`
Admin bool `json:"admin"`
}
// 사용자 정의 클레임을 포함한 토큰 생성
claims := MyClaims{
RegisteredClaims: jwt.RegisteredClaims{},
Username: "my_username",
Admin: true,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 서명을 위한 비밀 키
var mySigningKey = []byte("your-256-bit-secret")
// 문자열 형식으로 토큰 생성
strToken, err := token.SignedString(mySigningKey)
if err != nil {
log.Fatalf("오류 발생: %v", err)
}
fmt.Println(strToken)
위 코드에서는 등록된 클레임과 사용자 정의 정보를 포함하는 MyClaims
구조체를 정의했습니다. 그런 다음 여전히 SignedString
을 사용하여 토큰 문자열을 생성합니다.
토큰 구문 분석 및 유효성 검사하기
JWT의 구문 분석과 유효성 검사는 매우 중요합니다. 다음과 같이 수행할 수 있습니다:
// 위에서 정의한 MyClaims 구조체를 사용합니다.
tokenString := "your-JWT-string"
// tokenString을 구문 분석하는 데 jwt 패키지가 사용할 함수를 정의해야 합니다
keyFunc := func(t *jwt.Token) (interface{}, error) {
// 예상된 서명 알고리즘을 사용하는지 확인합니다
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("예기치 않은 서명 알고리즘: %v", t.Header["alg"])
}
// 서명에 사용된 jwt 토큰의 비밀 키를 []byte 형식으로 반환합니다. 이는 이전에 서명에 사용한 키와 일관성을 유지해야 합니다
return mySigningKey, nil
}
// 토큰 구문 분석
claims := &MyClaims{}
parsedToken, err := jwt.ParseWithClaims(tokenString, claims, keyFunc)
if err != nil {
log.Fatalf("구문 분석 오류: %v", err)
}
if !parsedToken.Valid {
log.Fatalf("유효하지 않은 토큰")
}
// 이 단계에서 parsedToken은 확인되었으며, 클레임을 읽을 수 있습니다
fmt.Printf("사용자: %s, 관리자: %v\n", claims.Username, claims.Admin)
위 코드에서는 토큰 문자열, MyClaims
인스턴스 및 키 함수 keyFunc
를 jwt.ParseWithClaims
함수에 제공했습니다. 이 함수는 서명을 확인하고 토큰을 구문 분석하여 토큰이 유효한 경우 claims
변수를 채웁니다.