GoLang에서 JWT토큰을 발급 받고, 검증 받는 방법 입니다.
라이브러리 받기
사용할 라이브러리 입니다. 이를 다운 받아 줍니다.
go get github.com/dgrijalva/jwt-go/v4
모듈에 라이브러리가 추가되었습니다. 이 글에는 uuid를 사용하기위해 uuid 라이브러리도 사용했습니다.
import (
"github.com/dgrijalva/jwt-go/v4"
"github.com/google/uuid"
)
라이브러리를 임포트 해줍니다.
토큰 발급
토큰 Claims
(토큰 내부 정보) 를 정의할 구조체를 만듭니다.
type AuthTokenClaims struct {
TokenUUID string `json:"tid"` // 토큰 UUID
UserID string `json:"id"` // 유저 ID
Name string `json:"name"` // 유저 이름
Email string `json:"mail"` // 유저 메일
UserUUID string `json:"uid"` // 유저 UUID
Role []string `json:"role"` // 유저 역할
jwt.StandardClaims // 표준 토큰 Claims
}
jwt.StandardClaims
는 JWT 표준에 미리 정의된 Claims입니다. 그 외에 자신이 토큰에 담을 정보를 정의하면 됩니다. 저는 간단하게 한 유저의 정보를 담았습니다.
at := AuthTokenClaims{
TokenUUID: uuid.NewString(),
UserID: "vompressor",
// uuid 참고 // https://www.vompressor.com/uuid-go/
UserUUID: uuid.NewString(),
Name: "vompressor",
Role: []string {"user", "admin"},
Email: "[email protected]",
StandardClaims: jwt.StandardClaims{
ExpiresAt: jwt.At(time.Now().Add(time.Minute * 15)), // 만료시간 15분
},
}
atoken := jwt.NewWithClaims(jwt.SigningMethodHS256, &at)
signedAuthToken, err := atoken.SignedString([]byte("SecretCode"))
jwt.NewWithClaims
함수에 원하는 서명 메소드와 자신이 정의한 구조체를 넣어줍니다. 이를 SignedString
함수에 전달하여 토큰을 서명합니다. 이 함수는 JWT 토큰을 String 으로 리턴합니다.
토큰 검증
토큰 검증은 jwt.ParseWithClaims
함수를 사용합니다. 기본적인 사용형태는 다음과 같습니다.
token, error := jwt.ParseWithClaims(tokenString, Claims, KeyFunction)
- tokenString - 검증할 토큰 문자열
- Claims - Claims 구조체 포인터
- KeyFunction - 토큰 서명 검증전 검증 핸들러
KeyFunction
은 다음처럼 사용합니다.
func (token *jwt.Token) (interface{}, error) {
if 잘못된 토큰일경우 {
return nil, err // secret 제공 거부, 에러 리턴
}
return []byte("SecretCode"), nil // secret 제공
}
ex)
key := func (token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
return nil, ErrUnexpectedSigningMethod
}
return []byte("SecretCode"), nil
}
tok, err := jwt.ParseWithClaims(token, claims, key)
println(tok.Valid)
토큰의 헤더를 검증해서 서명 알고리즘이 다를경우, 서명을 거부하도록 구성했습니다.
검증된, 만료되지 않은 토큰일 경우 tok.Valid
는 true
아닐경우 false
의 값을 가집니다.
사용 예시
Claims 정의
type AuthTokenClaims struct {
TokenUUID string `json:"tid"`
UserID string `json:"id"`
Name string `json:"name"`
Email string `json:"mail"`
UserUUID string `json:"uid"`
Role []string `json:"role"`
jwt.StandardClaims
}
토큰 발급
func TestTokenBuild(t *testing.T) {
at := AuthTokenClaims{
TokenUUID: uuid.NewString(),
UserID: "vompressor",
UserUUID: uuid.NewString(),
Name: "vompressor",
Role: []string {"user", "admin"},
Email: "[email protected]",
StandardClaims: jwt.StandardClaims{
ExpiresAt: jwt.At(time.Now().Add(time.Minute * 15)),
},
}
atoken := jwt.NewWithClaims(jwt.SigningMethodHS256, &at)
signedAuthToken, err := atoken.SignedString([]byte("SecretCode"))
if err != nil {
t.Fatal(err)
}
t.Log(signedAuthToken)
}
=== RUN TestTokenBuild
/home/dev/project/xlog/xuth/lib/xjwt/tokk_test.go:42: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0aWQiOiIzODdhYzBmYi00OTIzLTQ4MGYtYWEzZi1hZjY2NWVkMDdjMmYiLCJpZCI6InZvbXByZXNzb3IiLCJuYW1lIjoidm9tcHJlc3NvciIsIm1haWwiOiJ2b21wcmVzc29yQGdtYWlsLmNvbSIsInVpZCI6ImNmZTdjMjBiLWZkODUtNGQ2Zi05ZGNiLTFiZGZiOGM4ODI0MiIsInJvbGUiOlsidXNlciIsImFkbWluIl0sImV4cCI6MTYxNzk3Nzg2NS4xNzQ0NTA5fQ.fFOSMnPSYsxehmlR8blVA7LMr2zKQDCZblABqfCkg48
--- PASS: TestTokenBuild (0.00s)
PASS
토큰 검증
var token string = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0aWQiOiIzODdhYzBmYi00OTIzLTQ4MGYtYWEzZi1hZjY2NWVkMDdjMmYiLCJpZCI6InZvbXByZXNzb3IiLCJuYW1lIjoidm9tcHJlc3NvciIsIm1haWwiOiJ2b21wcmVzc29yQGdtYWlsLmNvbSIsInVpZCI6ImNmZTdjMjBiLWZkODUtNGQ2Zi05ZGNiLTFiZGZiOGM4ODI0MiIsInJvbGUiOlsidXNlciIsImFkbWluIl0sImV4cCI6MTYxNzk3Nzg2NS4xNzQ0NTA5fQ.fFOSMnPSYsxehmlR8blVA7LMr2zKQDCZblABqfCkg48"
func TestTokenVerfy(t *testing.T) {
claims := AuthTokenClaims{}
key := func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("Unexpected Signing Method")
}
return []byte("SecretCode"), nil
}
tok, err := jwt.ParseWithClaims(token, &claims, key)
if err != nil {
t.Fatal(err)
}
t.Logf("%t\n%#v", tok.Valid, claims)
}
정상적인 토큰
=== RUN TestTokenVerfy
/home/dev/project/xlog/xuth/lib/xjwt/tokk_test.go:60: true
xjwt_test.AuthTokenClaims{TokenUUID:"387ac0fb-4923-480f-aa3f-af665ed07c2f", UserID:"vompressor", Name:"vompressor", Email:"[email protected]", UserUUID:"cfe7c20b-fd85-4d6f-9dcb-1bdfb8c88242", Role:[]string{"user", "admin"}, StandardClaims:jwt.StandardClaims{Audience:jwt.ClaimStrings(nil), ExpiresAt:(*jwt.Time)(0xc0000b6288), ID:"", IssuedAt:(*jwt.Time)(nil), Issuer:"", NotBefore:(*jwt.Time)(nil), Subject:""}}
--- PASS: TestTokenVerfy (0.00s)
PASS
타임아웃 토큰
=== RUN TestTokenVerfy
/home/dev/project/xlog/xuth/lib/xjwt/tokk_test.go:58: token is expired by 18.843229885s
--- FAIL: TestTokenVerfy (0.00s)
FAIL
HS256 메소드가 아닐경우
=== RUN TestTokenVerfy
/home/dev/project/xlog/xuth/lib/xjwt/tokk_test.go:58: token is unverifiable: Keyfunc returned an error
--- FAIL: TestTokenVerfy (0.00s)
FAIL
jwt.UnverfiableTokenError
로 리턴된 에러입니다.