ogatasoが何か書きます

SWE(1年目)が勉強したことを書くヨ

Go言語でJWTを生成してみる

JWT認証はログインシステムの実装としてポピュラーですが、今回はGo言語でのJWTの生成にちょっとつまづいてしまったため、備忘録として書き残しておきます。以下のライブラリを利用してJWTを生成していきます。

github.com

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
}