【Go】Goで学ぶJWT ~検証編~
はじめに
前回の記事でJWTの概要や生成方法についてみていきました。
今回は、JWTを検証する方法についてを備忘録的に書いていこうと思います!
使用するライブラリは前回同様、github.com/golang-jwt/jwt/v5です。
この記事でわかること
-
ParseWithClaims()を使ったJWTの検証方法 -
ParseWithClaims()が内部で行う処理(署名検証・予約済みクレーム検証)の仕組み - 検証後のトークンから型アサーションを使って各フィールドにアクセスする方法
JWTを検証してみる
それでは早速、実際にJWTを検証してみましょう!
type MyClaims struct {
Name string `json:"name"`
jwt.RegisteredClaims
}
func main() {
// 中略
tokenString, err := token.SignedString(secretKey)
if err != nil {
log.Fatalln(err)
}
fmt.Println(tokenString)
// 1. JWTの検証
parsedToken, err := jwt.ParseWithClaims(
tokenString,
&MyClaims{},
func(token *jwt.Token) (any, error) {
return secretKey, nil
},
jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}),
jwt.WithLeeway(1*time.Minute),
jwt.WithIssuedAt(),
jwt.WithAudience("Test Audience"),
jwt.WithIssuer("Test Issuer"),
jwt.WithSubject("Test Subject"),
)
if err != nil {
log.Fatalln(err)
}
// 2. MyClaims型か検証
parsedTokenClaims, ok := parsedToken.Claims.(*MyClaims)
if !ok {
log.Fatalln("Failed to parse claims")
}
// 各種フィールドを標準出力
fmt.Println("Name: ", parsedTokenClaims.Name)
fmt.Println("Audience: ", parsedTokenClaims.RegisteredClaims.Audience)
fmt.Println("Issuer: ", parsedTokenClaims.RegisteredClaims.Issuer)
fmt.Println("Subject: ", parsedTokenClaims.RegisteredClaims.Subject)
fmt.Println("ID: ", parsedTokenClaims.RegisteredClaims.ID)
fmt.Println("IssuedAt: ", parsedTokenClaims.RegisteredClaims.IssuedAt)
fmt.Println("NotBefore: ", parsedTokenClaims.RegisteredClaims.NotBefore)
}
コードの解説
1. JWTの検証
github.com/golang-jwt/jwt/v5を使ってJWTを検証する場合、ParseWithClaims()を使います。シグネチャは以下の通りです。
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error)
引数にはそれぞれ以下のものが設定されます。
- 第一引数:トークン文字列
- 第二引数:クレームをマッピングする用の構造体
- 第三引数:署名検証用の鍵
- 第四引数:オプション
実装は以下の通りです。
// ParseWithClaims is a shortcut for NewParser().ParseWithClaims().
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error) {
return NewParser(options...).ParseWithClaims(tokenString, claims, keyFunc)
}
コメントにもあるように、ParseWithClaims()は、NewParser().ParseWithClaims()のショートカットとなっています。それぞれ、
-
NewParser():パースする際に内部で定義されているParser構造体のファクトリー関数 -
ParseWithClaims():実際にトークン文字列を検証するメソッド
このような役割を担っています。
さらに、ParseWithClaims()は主に以下の処理を行っています。
- トークン文字列をヘッダー・ペイロード・シグネチャ(署名)に分解しJSONデコード
- 署名検証のための鍵取得
- 署名検証
- 予約済みクレーム検証
このうち、「署名検証」では結合したヘッダーとペイロードを元に署名を計算して改竄されていないかをチェックし、「予約済みクレーム検証」では各クレームの妥当性を検証します。
「予約済みクレーム検証」では、expとnbfが必ずチェックされます。それ以外はオプションでチェックできますので、各種メソッドを用いてチェックしてもらう必要があるので注意しましょう。
2. MyClaims型か検証
ここでは、型アサーションを用いて自身が定義したJWT用構造体の型かどうかをチェックしています。
というのも、parsedToken.Claimsの型はjwt.Claimsというインターフェース型であり、そのままではフィールドにアクセスすることができません。
ですので、今回でいうとMyClaims型としてデータを取り出していいかのチェックを行う必要があるのです。
まとめ
今回はGoでJWTを検証する方法をみていきました。
github.com/golang-jwt/jwt/v5でJWTを検証するには、ParseWithClaims()を使います。この関数は内部で主に以下の処理を行っています。
- トークン文字列をヘッダー・ペイロード・シグネチャに分解
- 署名検証(改竄チェック)
- 予約済みクレームの妥当性検証
予約済みクレームの検証では、expとnbfは自動的にチェックされますが、それ以外のクレームはオプションを明示的に指定する必要がある点に注意しましょう。
また、ParseWithClaims()が返すparsedToken.Claimsはインターフェース型のため、自分で定義した構造体のフィールドに直接アクセスすることができません。型アサーションを使って具体型に変換することで、はじめて各フィールドを参照できるようになります。
Discussion