はじめに
この記事では、JWTを使うにあたっての概念理解や実装方法をまとめたものになります。
JWTとは?
JSON Web Token(JWT)とは、相互にデータをやり取りする主体同士で安全に情報を JSON オブジェクトとして転送するためのコンパクトで自己記述的な方法です。このJSON オブジェクトの情報はデジタル署名されているため、検証および信頼できるものになっています。
構成と作成方法
JWTは3つの部分で構成されています:
1. ヘッダー(Header):
トークンのタイプと使用されるハッシュアルゴリズムを指定
例:
{
"alg": "HS256",
"typ": "JWT"
}
このJSONはBase64Urlでエンコードされ、JWTの最初の部分を形成する。
例をエンコードした結果
eyJhbGciOiJIUZI1NiIsInR5cCI6IkpXVCJ9
2. ペイロード(Payload):
トークンに含まれるクレーム(要求)
よく使用されるクレーム名
1. iss (Issuer)
- トークンの発行者を表します
2.sub (Subject)
- トークンの対象者(主語)を表します。通常、ユーザーの ID などが格納されます
3.aud (Audience)
- トークンの受信者を表します。特定のサービスやアプリケーションの識別子が含まれます
4.exp (Expiration)
- トークンの有効期限(UNIX タイムスタンプ形式)を表します
5. nbf (Not Before)
- トークンの有効開始時刻を表します。この時刻より前はトークンが無効になります
6. iat (Issued At)
- トークンが発行された日時を表します
7. jti (JWT ID)
- トークンの一意な識別子を表し、リプレイ攻撃を防ぐために使用されることがあります
例:
{
"sub": "1234567890",
"name": "zono0013",
"iat": 1733929200,
"exp": 1734015600
}
このJSONもBase64Urlでエンコードされ、JWTの二つ目の部分を形成する。
例をエンコードした結果
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Inpvbm8wMDEzIiwiaWF0IjoxNzMzOTI5MjAwLCJleHAiOjE3MzQwMTU2MDB9
3. 署名(Signature):
トークンの整合性を保証
例:
-
1
2
で作成し、エンコードしたヘッダーとペイロードを.
で区切り一つの文字列にする - ヘッダーで指定したハッシュアルゴリズムと秘密鍵を使用して、署名を生成する
細かく説明すると、ハッシュ化する関数を秘密鍵を使って初期化を行い、
その後、ハッシュ化する。
gCz6YTVuuKy-B0GWS-isIs_zGmxcSPeANPB2i31_rJs
JWT構造
最終的に今回の例のJWTは以下のものになる
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // ヘッダー
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Inpvbm8wMDEzIiwiaWF0IjoxNzMzOTI5MjAwLCJleHAiOjE3MzQwMTU2MDB9. // ペイロード
gCz6YTVuuKy-B0GWS-isIs_zGmxcSPeANPB2i31_rJs // 署名
特徴
- JSONでやりとりできる点
- 著名があり改ざんを検出できる点
認証フロー
Signup
Signin
認証リクエスト
Goでの実装
トークンの生成
func generateToken(userID int) (string, error) {
claims := jwt.MapClaims{
"sub" : userID,
"name": userName,
"ita" : time.Now(),
"exp" : time.Now().Add(time.Hour * 24).Unix(), // 24時間有効
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// ヘッダーの追加(下記を参照してください)
return token.SignedString([]byte("JWT_SECRET_KEY"))
}
jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
を呼び出すと、JWT
ライブラリが以下のヘッダーを自動的に生成してくれます。
{
"alg": "HS256",
"typ": "JWT"
}
また、jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
の呼び出し後に以下のようなコードを追加することで、ヘッダーに情報を増やすことができる
// ヘッダーをカスタマイズ
token.Header["kid"] = "custom-key-id"
トークンの検証
func validateToken(tokenString string) (*jwt.Claims, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// 署名方法の検証
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method")
}
return []byte(os.Getenv("JWT_SECRET_KEY")), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
// 追加の検証(有効期限内か?を確認)
if exp, ok := claims["exp"].(float64); ok {
if time.Now().Unix() > int64(exp) {
return nil, errors.New("token has expired")
}
}
return &claims, nil
}
return nil, errors.New("invalid token")
}
参考文献