JWTの紹介

JWT (JSON Web Token) は、ウェブ上での認証のための簡潔で自己完結型の方法です。ヘッダー、ペイロード、署名の3つの部分から構成されています。ヘッダーにはトークンのタイプや暗号化アルゴリズム情報が含まれ、ペイロードには通過すべきデータが含まれます(通常は権限情報やユーザー識別情報など)、そして署名はトークンの整合性と有効性を検証するために使用されます。

JWTは主に2つのシナリオで使用されます:

  1. 認証:ユーザーがログインすると、その後の各リクエストにはJWTが含まれ、ユーザーは許可されたルート、サービス、リソースにアクセスできます。
  2. 情報交換:JWTは情報を安全にやり取りするための良い方法であり、例えば公開/秘密鍵ペアを使用してデジタル的に署名されるためです。

JWTデータ形式

JWTは情報を交換するためのコンパクトでURLセーフな方法です。JWTは実際には Header.Payload.Signature の3つの部分で構成され、それぞれがドット (.) で区切られています。次に、これら3つの部分のデータ形式について詳しく説明します。

1. ヘッダー

ヘッダーには通常2つの部分が含まれます:トークンのタイプ(通常は JWT)と使用される署名または暗号化アルゴリズム(例: HMAC SHA256RSA)。ヘッダーはJSONで表現され、次にBase64Urlを使用して文字列にエンコードされます。例:

{
  "alg": "HS256",
  "typ": "JWT"
}

エンコード後、次のような文字列を得ることができます:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. ペイロード

ペイロードには通常、エンティティ(通常はユーザー)に関する情報やその他のデータなど、一連のクレーム (claim) が含まれます。ペイロードには複数の事前定義のクレーム(登録済みクレームとも呼ばれる)やカスタムクレーム(プライベートクレーム)を含めることができます。

事前定義のクレームには以下が含まれます:

  • iss (発行者): Issuer
  • exp (有効期限): Expiration time
  • sub (主題): Subject
  • aud (対象): Audience
  • iat (発行時刻): Issued time
  • nbf (未使用日時): Effective time
  • jti (JWT ID): JWTの一意の識別子

例えば、以下のようなペイロードがあります(JSON形式):

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

この情報もBase64Urlでエンコードされ、次のような文字列が得られます:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0

3. 署名

署名部は、上記でエンコードされた2つの文字列を署名するために使用され、メッセージが伝送中に改ざんされていないことを検証します。まず、(HMAC SHA256アルゴリズムを使用する場合は)鍵を指定し、次にヘッダーで指定されたアルゴリズムを使用してヘッダーとペイロードを署名します。

例えば、以下のヘッダーとペイロードがある場合:

HeaderEncoded.HeaderPayload

これらを鍵を使用して署名する擬似コードは以下のようになります:

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secretKey)

生成された署名された文字列は以下のようなものです:

dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY

完全なJWT

これら3つの部分をドット (.) で区切って結合することで完全なJWTが形成されます:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY

JWT標準は非常に柔軟であり、ペイロードに必要な情報を格納することができます(ただし、JWTのサイズを削減するためおよびセキュリティ上の理由から、機密情報を格納すべきではありません)。また、署名を通じて情報の整合性を保証します。

Go JWTライブラリのインストール

Goコミュニティでは、JWTを処理するためのgithub.com/golang-jwt/jwtパッケージが提供されています。このライブラリをインストールするのは非常に簡単で、プロジェクトディレクトリで次のコマンドを実行するだけです:

go get -u github.com/golang-jwt/jwt/v5

インストールが完了すると、以下のようにimportでそれを含めることができます:

import "github.com/golang-jwt/jwt/v5"

簡単なトークンの作成

Go言語とHS256アルゴリズムを使用して簡単なJWTトークンを作成するには、以下の手順に従う必要があります:

まず、HS256アルゴリズムを使用して新しいTokenオブジェクトを生成します:

token := jwt.New(jwt.SigningMethodHS256)

次に、SignedStringメソッドを使用して、このトークンの文字列表現を生成します。署名に使用するキーを渡します。

var mySigningKey = []byte("your-256-bit-secret")
strToken, err := token.SignedString(mySigningKey)
if err != nil {
    log.Fatalf("エラーが発生しました: %v", err)
}
fmt.Println(strToken)

これにより、クレームを持たない簡単なトークンが生成されます。

パラメーター付きトークンの作成

JWTの主な機能の1つは情報を運ぶことです。この情報はクレームを介してトークンにエンコードされます。例えば、カスタムクレームを作成します:

// カスタムクレームの構造
type MyClaims struct {
	jwt.RegisteredClaims
	Username string `json:"username"`
	Admin    bool   `json:"admin"`
}

// カスタムクレームを持つトークンを作成
claims := MyClaims{
    RegisteredClaims: jwt.RegisteredClaims{},
    Username: "my_username",
	Admin: true,
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

// 署名に使用する秘密鍵
var mySigningKey = []byte("your-256-bit-secret")

// 文字列形式でトークンを生成
strToken, err := token.SignedString(mySigningKey)
if err != nil {
    log.Fatalf("エラーが発生しました: %v", err)
}

fmt.Println(strToken)

上記のコードでは、登録されたクレームとカスタム情報を含むMyClaims構造体を定義しました。その後、引き続きSignedStringを使用してトークン文字列を生成しています。

トークンの解析と検証

JWTの解析と検証は非常に重要です。以下のように行うことができます:

// 同じMyClaims構造を使用します
tokenString := "your-JWT-string"

// tokenStringを解析するためにjwtパッケージが使用する関数を定義する必要があります
keyFunc := func(t *jwt.Token) (interface{}, error) {
	// 予期しない署名メソッドが使用されていないことを検証
	if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
	    return nil, fmt.Errorf("予期しない署名メソッド: %v", t.Header["alg"])
	}
	// jwtトークンのための秘密鍵を返します。署名時に使用したキーと整合する[]byte形式である必要があります
	return mySigningKey, nil
}

// トークンの解析
claims := &MyClaims{}
parsedToken, err := jwt.ParseWithClaims(tokenString, claims, keyFunc)
if err != nil {
	log.Fatalf("解析エラー: %v", err)
}

if !parsedToken.Valid {
    log.Fatalf("無効なトークン")
}

// この段階で、parsedTokenは検証されており、クレームを読むことができます
fmt.Printf("ユーザー: %s, 管理者: %v\n", claims.Username, claims.Admin)

上記のコードでは、token文字列、MyClaimsインスタンス、およびキー関数keyFuncjwt.ParseWithClaims関数に提供しました。この関数は署名を検証し、トークンを解析し、トークンが有効であればclaims変数を埋めます。