JWTとは?
- Json Web Tokenの略。詳しくはjwt.ioにいくのが一番手っ取り早い
- header.payload.signatureの3つで構成される
- 例
- eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJpc3MiOiAieXV5YW4iLCAiaWQiOiAiMTIzNDU2In0.rbpRDicBTBVeqLvTA-Pw1LKZyb7u8Xqid0rgSNGThDY
- headerは署名化の方法
- 上記のJWTの
eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9
の部分 - これは
{"alg": "HS256", "typ": "JWT"}
をBase64URLエンコードし、=
を取り除いたもの
- 上記のJWTの
- payloadはトークンの中身。idやemailなどある程度好きなものを埋め込める
- 上記のJWTの
eyJpc3MiOiAieXV5YW4iLCAiaWQiOiAiMTIzNDU2In0
の部分 - これは
{"iss": "yuyan", "id": "123456"}
をBase64URLエンコードし、=
を取り除いたもの
- 上記のJWTの
- signatureはheader.payloadを鍵を使って署名化したもの。
- 上記 JWTの
rbpRDicBTBVeqLvTA-Pw1LKZyb7u8Xqid0rgSNGThDY
の部分 - headerの
{"alg": "HS256", "typ": "JWT"}
と、payloadの{"iss": "yuyan", "id": "123456"}
を、鍵secret-key
でHMAC SHA256で署名化し、それをさらにBase64URLエンコードし、=
を取り除いたもの
- 上記 JWTの
何に使うの?
- 今回は認証トークンに使う
- payloadにidを埋め込み、認証トークンとして使う
なんでJWTを使うの?
- 個人的な考えは、便利だから
- header, payloadの内容は簡単に取り出せる
- base64 urlでエンコードされてるだけ
- payloadに有効期限を含めれば、フロント側で有効期限のチェックができる
- あとpayloadからidを取得すればいいので、データベースにアクセスしなくていい
注意点
- JWTのheader, payloadの内容は簡単に取り出せるので個人情報や秘密情報は含めない方がいい(特にパスワードをpayloadに、なんてのは絶対ダメ!)
- JWT自体が有効期限を持っている、という形なので個別ログアウトを実装するには何か仕組みが必要
- 例えばJWTの有効期限を24時間に設定すると24時間はトークンが有効になる。
- 全てのトークンを無効にするならコードの修正でできるけど、あるトークンだけ無効にしたい、って場合難しい(MemcachedとかRedisを使う?)
使用パッケージ
JWTを使う場合、既存のパッケージを使うことが多いと思います。
今回はlestrrat-go/jwx
を使います。理由はissやsubのチェックがあるからです。
別に「これじゃなければダメだ!」とかないので、更新されてるか、とかスターの数とかみて好きなものを使ってください。
JWTを作ってみた!
ごちゃごちゃ書きましたが、理解するには自分で作ってみるのが早いです。
というわけで作ってみました。
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"strings"
)
var (
hs256Key = []byte("secret-key")
headerJSON = `{"alg": "HS256", "typ": "JWT"}`
payloadJSON = `{"iss": "yuyan", "id": "123456"}`
)
func main() {
// headerをbase64URLでエンコード
base64Header := base64.URLEncoding.EncodeToString([]byte(headerJSON))
// base64Headerから = を取り除く
header := strings.ReplaceAll(base64Header, "=", "")
// payloadをbase64URLでエンコードする
base64Payload := base64.URLEncoding.EncodeToString([]byte(payloadJSON))
// base64Payloadから = を取り除く
payload := strings.ReplaceAll(base64Payload, "=", "")
// HMAC SHA256で header.payload を署名化
mac := hmac.New(sha256.New, hs256Key)
mac.Write([]byte(fmt.Sprintf("%s.%s", header, payload)))
// 署名化した値をbase64URLでエンコードする
base64Signature := base64.URLEncoding.EncodeToString((mac.Sum(nil)))
// base64Signatureから = を取り除く
signature := strings.ReplaceAll(base64Signature, "=", "")
// header.payload.signatureでJWTが完成!
jwt := fmt.Sprintf("%s.%s.%s", header, payload, signature)
fmt.Println(jwt)
}
func decodeJWT(token string, hs256Key []byte) bool {
// jwtをheader, payload, signatureに分ける
arr := strings.Split(token, ".")
if len(arr) != 3 {
fmt.Println("invalid token")
return false
}
header := arr[0]
payload := arr[1]
signature := arr[2]
mac := hmac.New(sha256.New, hs256Key)
mac.Write([]byte(fmt.Sprintf("%s.%s", header, payload)))
verify := strings.ReplaceAll(base64.URLEncoding.EncodeToString(mac.Sum(nil)), "=", "")
if !hmac.Equal([]byte(signature), []byte(verify)) {
fmt.Println("invalid hs256key")
return false
}
return true
}
こんな感じです。
お疲れ様です。
ありがとうございました!