JWT認証はログインシステムの実装としてポピュラーですが、今回はGo言語でのJWTの生成にちょっとつまづいてしまったため、備忘録として書き残しておきます。以下のライブラリを利用してJWTを生成していきます。
JWTとは
初めに、JWTとは何かについて整理しておきます。JWTとはJson Web Tokenの略で、読み方は「ジョット」とからしいのですが、私はそのまま「ジェーダブリューティー」と呼んでしまっています。実物をお見せするとこんな感じです。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJncmVldGluZ3MiOiJIZWxsbywgV29ybGQhIn0.6Yh5DR_8xnjywt9K_ITKDip5r2wKa0_TYMFXJ5iXDaU
ピリオド(.)によって3つの部分に分かれていることを確認することができます。最初の部分はヘッダとして暗号アルゴリズムの名前およびトークンのタイプ(基本的には大文字で"JWT"と指定するのが推奨されている)が保存されており、真ん中の部分はペイロードが、それぞれBase64urlエンコード形式で保持されています。最後の部分はヘッダとペイロードを暗号化することで生成される署名になっています。JWTの中身は以下のようなデコーダーで確認することができます。 web-toolbox.dev
JWTはRFC 7519で仕様定義されているので、より詳しく知りたい方は読んでみるのも良いでしょう。 tex2e.github.io
JWTを作成する
JWTは以下のコードで生成できます。Claimsはペイロードに相当する部分で、jwt.NewWithClaims
でClaims付きでJWTを作ります。その後、token.SignedString([]byte("secret"))
で署名部分を加えてJWTを完成させます。これはサンプルなので鍵を"secret"としていますが、本番運用では$ openssl rand -hex 32
などで生成した乱数を利用するのが好ましいでしょう。
func createJWT() string { claims := jwt.MapClaims{ "greetings": "Hello, World!", } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenWithSignature, _ := token.SignedString([]byte("secret")) return tokenWithSignature }
JWTを検証、およびペイロードの中身を確認
jwt.Parse
を利用してトークンをパースします。第二引数の関数にはパースされたトークンが渡され、初めにトークンの暗号方式が生成時に利用したjwt.SigningMethodHMAC
と同じものかどうかを検証したのち、鍵を渡しています。
ペイロードはtoken.Claims
にあり、jwt.MapClaims
に型アサーションした上で中身を取ることができます(私はここがわかりにくくてつまづいていました...)。また、JWTの署名が正しいかどうかはtoken.Valid
で確認することができます(確認を忘れるとJWTの意味がなくなってしまうので要注意)。試しに以下のコードの"secret"を他の文字列に変えてみて、falseになっていればきちんとtoken.Valid
が機能していることを確認できます。
func verifyJWT(tokenString string) { token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return []byte("secret"), nil }) claims := token.Claims.(jwt.MapClaims) fmt.Println(claims["greeting"]) // Hello, world! fmt.Println(token.Valid) // true }