はじめに
Webの認証で扱われるJWT。誰かが作成した認証・認可の部品を利用する事で自然に触れている。自然に触れているけど、JWTって何?どうやって作ってるの?セキュリティ周りなんでそれで大丈夫なの?って所がいまいちイメージできていなかった。今回、JWTの所を改めて勉強することにしたので、記事にして残しておきます。
JWTって何?
JWTとはJsonWebTokenの略で、ジョットと読みます。RFC7519で定義されたインターネット標準の認証方式です。公式リファレンスの抜粋では下記のように定義されています。
[日本語訳]
JSON Web Token (JWT) は、コンパクトでURLセーフな表現方法です。JWT は,2 者間で転送されるクレームを表すコンパクトな URL セーフな手段である。
JWTのクレームはJSONオブジェクトとしてエンコードされ、JSONウェブ署名(JWS)構造のペイロードとして、またはJSONウェブ暗号(JWE)構造の平文として使用されます。
暗号(JWE)構造のペイロードとして使用されるJSONオブジェクトとして符号化され、クレームを電子的に署名するか、メッセージメッセージ認証コード(MAC)で署名または整合性保護され(MAC)や暗号化を可能にします。
→結局何なの??って感じですね。。。
改ざんやなりすましを検知するために、電子署名を利用してるよって位の認識で一旦OKです!!
JWTの生成、なりすまし検知方法
■JWTの生成(ログイン時)
ログイン生成時、ユーザIDとパスワードを元に認証実施。認証後、ユーザIDを秘密鍵を用いて暗号化し、トークン発行。
■JWTによる認可処理(API実行時)
ログイン後、他API実行時のHeaderにJWTを付与。JWTを復号化しユーザIDを取得。ユーザIDの認可処理を実施し、そのAPI自体を叩けるかを確認。確認後、それぞれに準じた処理を実施する。
ざっくり、上記のような事をやっています!!
JWTの構造
JWTはヘッダー、ペイロード、署名の3つから成り立つ。
- ヘッダー:トークンのタイプと署名アルゴリズムを定義
- ペイロード:トークン発行者、トークンの有効期限などを定義。アプリケーションで利用する値を定義する事が多いです。
- 署名:改ざんやなりすましを防止の電子署名を定義
では具体的にGolangの実装を元に、JWT生成と認証を実施していきましょう!!!
JWTの生成
package main
import (
"fmt"
"time"
"github.com/dgrijalva/jwt-go"
)
func main() {
// JWTに付与する構造体
claims := jwt.MapClaims{
"user_id": "user_id1234",
"exp": time.Now().Add(time.Hour * 72).Unix(), // 72時間が有効期限
}
// ヘッダーとペイロード生成
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// トークンに署名を付与
accessToken, _ := token.SignedString([]byte("ACCESS_SECRET_KEY"))
fmt.Println("accessToken:", accessToken)
}
~/go/src/jikken$ go run main.go
accessToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjQyNTk1NDUsInVzZXJfaWQiOiJ1c2VyX2lkMTIzNCJ9.CFmswwCkYu0wtPAqGkmuNovFLHSuneGLw8Qt0R7AKCE
→JWTの生成ができましたね!!では次に生成したJWTを元に認可処理を実施しましょう!!
JWTによる認可処理
package main
import (
"fmt"
"github.com/dgrijalva/jwt-go"
)
func main() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjQyNTk1NDUsInVzZXJfaWQiOiJ1c2VyX2lkMTIzNCJ9.CFmswwCkYu0wtPAqGkmuNovFLHSuneGLw8Qt0R7AKCE"
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: %v", token.Header["alg"])
}
return []byte("ACCESS_SECRET_KEY"), nil
})
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
fmt.Printf("user_id: %v\n", string(claims["user_id"].(string)))
fmt.Printf("exp: %v\n", int64(claims["exp"].(float64)))
} else {
fmt.Println(err)
}
}
~/go/src/jikken$ go run main.go
user_id: user_id1234
exp: 1664259545
→できましたね!!これで完了です!!
次回はWebシステムでJWTの生成から認証処理を実施していきたいと思います!!!