JWTの仕組み
JWT(JSON Web Token)は、ユーザー認証情報を含む自己完結型のトークンを扱います。読み方は ”ジョット” です。
ヘッダー、ペイロード、署名の3部分で構成され、秘密鍵で署名し、トークンはクライアント側で保存され、リクエスト時に使用し、サーバ側で検証します。
JWTの主なメリットとしては、サーバ側で状態(セッション)を管理する必要がないためスケーラビリティに優れている点だと思います。
JWT認証を試してみる
今回は Golang で実装してみますが、echoというフレームワークを使用します。
先にコードを記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
package main import ( "net/http" "os" "time" "github.com/golang-jwt/jwt/v4" echojwt "github.com/labstack/echo-jwt" "github.com/labstack/echo/v4" ) var signingKey = []byte(func() string { if v := os.Getenv("JWT_SECRET"); v != "" { return v } else { return "c20qHTdPk9tQlJzKHJz9+NC/4p3fum9yrfEbOWuUZys=" // JWT_SECRET は openssl rand -base64 32 で生成 } }()) func main() { e := echo.New() // Public routes e.GET("/home/*", home) e.POST("/login", login) // JWT Middleware for restricted routes r := e.Group("") r.Use(echojwt.WithConfig(echojwt.Config{ SigningKey: signingKey, })) r.GET("/restricted", restricted) e.Logger.Fatal(e.Start(":8080")) } func home(c echo.Context) error { return c.String(http.StatusOK, "Welcome to the home page!") } func restricted(c echo.Context) error { return c.String(http.StatusOK, "Welcome to the restricted area!") } func login(c echo.Context) error { // ※実際のアプリケーションでは、ここでユーザーを認証します※ username := c.FormValue("username") password := c.FormValue("password") if username == "user" && password == "password" { // トークン生成開始 token := jwt.New(jwt.SigningMethodHS256) // クレームを設定 claims := token.Claims.(jwt.MapClaims) claims["name"] = "Admin" claims["admin"] = true claims["exp"] = time.Now().Add(time.Hour * 72).Unix() // エンコードされたトークンを生成し、レスポンスとして送信します。 t, err := token.SignedString(signingKey) if err != nil { return err } // クッキーにトークンを設定 cookie := new(http.Cookie) cookie.Name = "token" cookie.Value = t cookie.Expires = time.Now().Add(72 * time.Hour) cookie.HttpOnly = true cookie.Secure = true cookie.SameSite = http.SameSiteStrictMode c.SetCookie(cookie) return c.JSON(http.StatusOK, echo.Map{ "message": "トークンは " + t, }) } return echo.ErrUnauthorized } |
それでは、プログラムを実行してJWTから返ってくるトークンを確認します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
% go run main.go ____ __ / __/___/ / ___ / _// __/ _ \/ _ \ /___/\__/_//_/\___/ v4.13.3 High performance, minimalist Go web framework https://echo.labstack.com ____________________________________O/_______ O\ ⇨ http server started on [::]:8080 #### 別ターミナルで % curl -X POST http://localhost:8080/login -d "username=user&password=password" {"message":"token is eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNzM2NjkwNTY1LCJuYW1lIjoiQWRtaW4ifQ.riW-46p5W7bnjiqBcHlOocD2LEfRl588ie78ZtEmuPc"} |
JWTのトークンの中身はbase64でデコードすれば確認可能なので見てみます。
1 2 3 4 5 6 |
% echo eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 | base64 -d {"alg":"HS256","typ":"JWT"}% % echo eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNzM2Njg1MTY1LCJuYW1lIjoiQWRtaW4ifQ | base64 -d {"admin":true,"exp":1736685165,"name":"Admin"% |
また、 https://jwt.io/ でトークンの検証を行うこともできます。”Signature Verified” と表示されていれば検証がうまくいっています。
テキトーな例ですが、結局中身を簡単に確認できるわけなので以下の注意が必要かと思います。
- JWTには個人情報などは含めない
- トークンには期限を設定する
そして、ヘッダとペイロードが変更されても、署名が一致しないため成りすましを防止できます。
ちなみにJWTには登録済みのクレームがあるのでそちらもご確認ください。(iss, sub, exp…)
https://tex2e.github.io/rfc-translater/html/rfc7519.html#4-1–Registered-Claim-Names
上記では入れてませんが sub にユーザ固有の識別子をいれるなどすると良いと思います。
まとめ
JWTは多くのライブラリやフレームワークでサポートされているため、実装が容易であり、複数のサービス間で認証情報を共有するのに適しており、マイクロサービスアーキテクチャにおいても有効な手段となります。