gin-jwt の使い方 (その2)

こちらのサンプルを改造しました。
[gin-jwt の使い方](https://qiita.com/ekzemplaro/items/65e63899c087fee4b102)

次の３人のユーザーがアクセスできるようにしました。

|User |Passwrd |
|---|---|
|scott |secret01 |
|john |secret02 |
|mary |secret03 |

次を改造します。
>Authenticator:
>Authorizator:

## サーバープログラム

```go:server.go
// ---------------------------------------------------------------
//
//	server.go
//
//					Feb/03/2021
// ---------------------------------------------------------------
package main

import (
	"log"
	"net/http"
	"os"
	"time"

	jwt "github.com/appleboy/gin-jwt/v2"
	"github.com/gin-gonic/gin"
)

type login struct {
	Username string `form:"username" json:"username" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

var identityKey = "id"

func helloHandler(c *gin.Context) {
	claims := jwt.ExtractClaims(c)
	user, _ := c.Get(identityKey)
	c.JSON(200, gin.H{
		"userID":   claims[identityKey],
		"userName": user.(*User).UserName,
//		"text":     "Hello World.",
		"text":     "Hello World. こんにちは",
	})
}

// User demo
type User struct {
	UserName  string
	FirstName string
	LastName  string
}

// ---------------------------------------------------------------
func main() {
	port := os.Getenv("PORT")
	r := gin.New()
	r.Use(gin.Logger())
	r.Use(gin.Recovery())

	if port == "" {
		port = "8000"
	}

	// the jwt middleware
	authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
		Realm:       "test zone",
		Key:         []byte("secret key"),
		Timeout:     time.Hour,
		MaxRefresh:  time.Hour,
		IdentityKey: identityKey,
		PayloadFunc: func(data interface{}) jwt.MapClaims {
			if v, ok := data.(*User); ok {
				return jwt.MapClaims{
					identityKey: v.UserName,
				}
			}
			return jwt.MapClaims{}
		},
		IdentityHandler: func(c *gin.Context) interface{} {
			claims := jwt.ExtractClaims(c)
			return &User{
				UserName: claims[identityKey].(string),
			}
		},
		Authenticator: func(c *gin.Context) (interface{}, error) {
			var loginVals login
			if err := c.ShouldBind(&loginVals); err != nil {
				return "", jwt.ErrMissingLoginValues
			}
			userID := loginVals.Username
			password := loginVals.Password

			if (userID == "scott" && password == "secret01") ||
			 (userID == "john" && password == "secret02") ||
			 (userID == "mary" && password == "secret03") {
				return &User{
					UserName:  userID,
					LastName:  "Bo-Yi",
					FirstName: "Wu",
				}, nil
			}

			return nil, jwt.ErrFailedAuthentication
		},
		Authorizator: func(data interface{}, c *gin.Context) bool {
			if v, ok := data.(*User); ok &&
				(v.UserName == "scott" ||
				v.UserName == "john" ||
				v.UserName == "mary")  {
				return true
			}

			return false
		},
		Unauthorized: func(c *gin.Context, code int, message string) {
			c.JSON(code, gin.H{
				"code":    code,
				"message": message,
			})
		},
		// TokenLookup is a string in the form of "<source>:<name>" that is used
		// to extract token from the request.
		// Optional. Default value "header:Authorization".
		// Possible values:
		// - "header:<name>"
		// - "query:<name>"
		// - "cookie:<name>"
		// - "param:<name>"
		TokenLookup: "header: Authorization, query: token, cookie: jwt",
		// TokenLookup: "query:token",
		// TokenLookup: "cookie:token",

		// TokenHeadName is a string in the header. Default value is "Bearer"
		TokenHeadName: "Bearer",

		// TimeFunc provides the current time. You can override it to use another time value. This is useful for testing or if your server uses a different time zone than your tokens.
		TimeFunc: time.Now,
	})

	if err != nil {
		log.Fatal("JWT Error:" + err.Error())
	}

	// When you use jwt.New(), the function is already automatically called for checking,
	// which means you don't need to call it again.
	errInit := authMiddleware.MiddlewareInit()

	if errInit != nil {
		log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error())
	}

	r.POST("/login", authMiddleware.LoginHandler)

	r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) {
		claims := jwt.ExtractClaims(c)
		log.Printf("NoRoute claims: %#v\n", claims)
		c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
	})

	auth := r.Group("/auth")
	// Refresh time can be longer than token timeout
	auth.GET("/refresh_token", authMiddleware.RefreshHandler)
	auth.Use(authMiddleware.MiddlewareFunc())
	{
		auth.GET("/hello", helloHandler)
	}

	if err := http.ListenAndServe(":"+port, r); err != nil {
		log.Fatal(err)
	}
}

// ---------------------------------------------------------------
```

## サーバーの起動

```bash
go run server.go
```

## トークンを取得

```bash:get_token.sh
#
http :8000/login username=scott password=secret01  > scott.json
jq . scott.json
#
http :8000/login username=john password=secret02  > john.json
jq . john.json
#
http :8000/login username=mary password=secret03  > mary.json
jq . mary.json
#
```

## API アクセス

```bash:go_hello.sh
#
token=`jq .token scott.json | sed 's/"//g'`
http :8000/auth/hello "Authorization:Bearer ${token}"
#
token=`jq .token john.json | sed 's/"//g'`
http :8000/auth/hello "Authorization:Bearer ${token}"
#
token=`jq .token mary.json | sed 's/"//g'`
http :8000/auth/hello "Authorization:Bearer ${token}"
#
```

## 確認したバージョン

```text
$ go version
go version go1.20.3 linux/amd64
```