たいさブログ

taisa's engineer blog

Go

Golangを使ってJWTを15分で理解する

投稿日:


JWTとは

JWT(ジョットと言うらしい)はJSON Web Tokenの略で、JSONをベースとしたアクセストークンのためのオープン標準 (RFC 7519) です。色々記事を見ましたが、最終的にWikipediaが分かりやすく一番参考にしました。

https://ja.wikipedia.org/wiki/JSON_Web_Token

JWTの構造

JWTは以下の3つの要素をピリオドで区切った文字列で構成されます。

ヘッダー

署名生成に使用するアルゴリズムを格納します。下記のHS256は、このトークンがHMAC-SHA256で署名されていることを示しています。署名アルゴリズムとしては、SHA-256を使用したHMAC (HS256) や、SHA-256を使用したRSA署名 (RS256) がよく用いられます。

ペイロード

認証情報などのクレームを格納します。クレームとはペイロードに含める以下のような標準フィールド(クレーム)を指します。JWTの仕様では、トークンに一般的に含まれる7つの標準フィールドが定義されています。また用途に応じた独自のカスタムフィールドを含むこともできます。下記の例では、トークン発行日時を示す標準のクレーム (iat) と、カスタムクレーム (loggedInAs) を格納しています。

7つのペイロードの標準クレーム

署名

トークン検証用の署名です。署名はヘッダーとペイロードをBase64urlエンコーディングしてピリオドで結合したものから生成します。署名はヘッダーで指定された暗号化アルゴリズムにより生成されます。下記はHMAC-SHA256形式でのコード例です。

JWTを使用するにあたって

JWTはトークンが返され、それをローカルに保存して利用します(主にlocal storageやsession storageが用いられますが、セッションIDのようにCookieを用いる場合もあります。) 認証時にはAuthorizationヘッダーでBearerスキーマを利用します。またサーバー上に認証状態を保持しないステートレスな認証方式です。その為JWT単体ではトークンを無効にすることが出来ません。サーバーに状態を保持すれば可能ですが、その場合ステートレスの利点は失われることになります。

さて、ここまではほぼ Wikipedia に書いてある内容そのままです。ここから実際にGo/GinJWT Middlewareを使って実際の動作を確認してみます。

Go/GinJWT Middlewareを使った動作確認

利用するJWT Middlewareについて

ここでは、「https://github.com/gin-gonic/gin」 を使う前提で、次のMiddlewareを利用します。「https://github.com/appleboy/gin-jwt」。このMiddlewareは、auth_jwt.goの1ファイルでで構成されていて、「https://github.com/dgrijalva/jwt-go」Gin用に薄くラップしたものです。jwt-goはトークンを作成したりパースしたり様々な機能が用意されています。

サンプルソース

サンプルソースは、https://github.com/appleboy/gin-jwt/blob/master/README.md に載っているのでこれを元に確認します。処理は大きく「ログイン時にToken発行する」と「トークン認証&処理実行する」の2種類あります。

ログイン時にToken発行する

ログイン時にTokenを発行する処理は、LoginHandlerです。Routerでは次のように定義しています。LoginHandlerではAuthenticatorPayloadFuncが呼ばれる為、Middlewareにてこれらを実装する必要があります。

Authenticatorはログイン認証の為の関数です。例では固定値が設定されていますが、実際は主にDBから値を取得することになると思います。PayloadFuncはペイロードに含めるクレームを設定します。ペイロードには任意のクレームを追加可能なので、ログインIDとなるuserIDをセットしています。

ログイン時にアクセストークンを取得するにはサーバ起動後に以下のコマンドを実行します。

実行すると次のような結果が得られます。

LoginHandlerの処理を見てみる

ここであらためてauth_jwt.goLoginHandlerの処理を見てみると内部で何をやっているかが具体的に分かります。エラーチェックなどを省いて主要なところだけ確認してみます。

上記middlewareで実装しているAuthenticatorを呼んでログイン認証します。

ログイン認証が通ったらクレームを取得します。

上記で実装しているPayloadFuncを呼んで独自クレームを設定します。

更にペイロードに必要なクレーム情報を設定します。expはトークン切れタイムで、orig_iatはトークン生成タイムです。

最後に署名付きトークンを生成してレスポンスとして返します。

これらを組み合わせたLoginHandlerの実際の処理です。

払い出されたTokenを検証してみる

払い出されたTokenを検証してみます。トークンはピリオドで分割し3つに分けることができます。

  • ヘッダー:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  • ペイロード:eyJleHAiOjE1NjY4Nzk5NzgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTU2Njg3NjM3OH0
  • 署名:D0z_y8vLEeQW_mtgOCw6gfrmz6eSGfW6uOG7KoEaMAo

ヘッダーとペイロードはbase64でエンコードされているだけなので以下のコマンドで確認することができます。

・ヘッダー

・ペイロード

・署名は暗号化されているので当然復元できません。

またトークンを確認するのに便利なサイトがあります。このサイトを利用するとJWT認証の確認が簡単にできます。認証ができていないと、Invalid Signatureとなりますが、シークレットキーに今回使った文字列secret keyと入力し今回のトークンを貼り付けるとSignature Verifiedと認証成功が確認できます。
https://jwt.io/

・認証NG

・認証OK

トークン認証&処理実行する

次にトークン認証と認証後の処理の実行をみてみます。auth_jwt.goではMiddlewareFuncを呼び出します。RouterにはauthMiddleware.MiddlewareFunc()をかまし、その中に実行したいRouteを設定します。

middleware側では、IdentityHandlerAuthorizatorをあらかじめ実装しておく必要があります。

トークンを使ってリクエストを投げるとリクエストが実行されます。

正常にレスポンスが取得できました。

試しに署名の最後の4文字をaaaaに変更してみると署名の認証エラーであるsignature is invalidが返ってきます。

MiddlewareFuncの処理を見てみる

では実際中ではどのような処理が行われているかauth_jwt.goMiddlewareFuncの主要処理をみてみます。

トークンからクレームを取得します。

クレーム内のexpをチェックしトークンが有効かをチェックします。

ミドルウェアで実装しているIdentityHandlerを呼び出し、クレームからユーザ情報を取得します。

ミドルウェアで実装しているAuthorizatorを呼び出しIDが一致していたら認証OKとなります。

MiddlewareFuncの全処理です。

これで一通りの動作確認ができました。

まとめ

はじめ知らないワードが出てきて理解しきれなかったのですが、実際に触ってみることでJWTについてある程度理解できました。触ってみてステートレスであり、ステートレスであるがゆえにJWT単体ではトークンを無効にすることが出来ないという理由も実感できました。

その他の参考記事

https://qiita.com/k_k_hogetaro/items/0c97f42ecb8207767db2


-Go

執筆者:

関連記事

Go言語 GORM+GinでTODOリストを作ってみた

前回の「Go言語 ORMライブラリ GORMの使い方」に続いて「GORM+Gin」でTODOリストを作ってみました。使い方は「GitHubのREADME」を参考にしました。できたものは下記URLから確 …

Golang 1.13 released! The difference from 1.12 to 1.13

Golang 1.13 was released in 3 September 2019. This post has difference from 1.12 to 1.13. I check th …

Go言語 ORMライブラリ GORMの使い方

Go言語 ORMライブラリのGORMの簡単な使い方を確認してみました。また、公式ドキュメントにしっかりと使い方が書いてありますので基本的にはそちらを参考にしてもらえればと思います(すべてではないですが …

Go言語 GORM+GinでTODOリストのAPIを作ってみた

前回の「Go言語 GORM+GinでTODOリストを作ってみた」に続いて「GORM+Gin」でTODOリストのAPIを作ってみました。ソースコードは前回からの差分だけを記載しています。できたものは下記 …

Top