GoでJWT生成/認証を実装する。(ロジック編)

# はじめに
Webの認証で扱われるJWT。誰かが作成した認証・認可の部品を利用する事で自然に触れている。自然に触れているけど、JWTって何？どうやって作ってるの？セキュリティ周りなんでそれで大丈夫なの？って所がいまいちイメージできていなかった。今回、JWTの所を改めて勉強することにしたので、記事にして残しておきます。

## JWTって何？
JWTとはJsonWebTokenの略で、ジョットと読みます。[RFC7519](https://www.rfc-editor.org/rfc/rfc7519)で定義されたインターネット標準の認証方式です。公式リファレンスの抜粋では下記のように定義されています。
![スクリーンショット 2022-09-24 14.34.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/8c6af326-de3c-269b-8166-d2d620942716.png)
[日本語訳]
```
JSON Web Token (JWT) は、コンパクトでURLセーフな表現方法です。JWT は，2 者間で転送されるクレームを表すコンパクトな URL セーフな手段である。
JWTのクレームはJSONオブジェクトとしてエンコードされ、JSONウェブ署名（JWS）構造のペイロードとして、またはJSONウェブ暗号（JWE）構造の平文として使用されます。
暗号（JWE）構造のペイロードとして使用されるJSONオブジェクトとして符号化され、クレームを電子的に署名するか、メッセージメッセージ認証コード(MAC)で署名または整合性保護され(MAC)や暗号化を可能にします。
```
→結局何なの？？って感じですね。。。
改ざんやなりすましを検知するために、電子署名を利用してるよって位の認識で一旦OKです！！

## JWTの生成、なりすまし検知方法
■JWTの生成(ログイン時)
ログイン生成時、ユーザIDとパスワードを元に認証実施。認証後、ユーザIDを秘密鍵を用いて暗号化し、トークン発行。
![スクリーンショット 2022-09-24 14.56.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/898d9a4d-ba01-c5c1-c335-2c4fd9f561bc.png)

■JWTによる認可処理(API実行時)
ログイン後、他API実行時のHeaderにJWTを付与。JWTを復号化しユーザIDを取得。ユーザIDの認可処理を実施し、そのAPI自体を叩けるかを確認。確認後、それぞれに準じた処理を実施する。
![スクリーンショット 2022-09-24 15.05.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/887f393f-80a9-ec43-2353-f59911eabf17.png)

ざっくり、上記のような事をやっています！！

## JWTの構造
JWTはヘッダー、ペイロード、署名の3つから成り立つ。
<ol>
<li>ヘッダー：トークンのタイプと署名アルゴリズムを定義</li>
<li>ペイロード：トークン発行者、トークンの有効期限などを定義。アプリケーションで利用する値を定義する事が多いです。</li>
<li>署名：改ざんやなりすましを防止の電子署名を定義</li>
</ol>

では具体的にGolangの実装を元に、JWT生成と認証を実施していきましょう！！！


## JWTの生成

```golang:main.go
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) 
}

```

```shell:実行結果
~/go/src/jikken$ go run main.go
accessToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjQyNTk1NDUsInVzZXJfaWQiOiJ1c2VyX2lkMTIzNCJ9.CFmswwCkYu0wtPAqGkmuNovFLHSuneGLw8Qt0R7AKCE
```
→JWTの生成ができましたね！！では次に生成したJWTを元に認可処理を実施しましょう！！

## JWTによる認可処理

```golang:main.go
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の生成から認証処理を実施していきたいと思います！！！