目次
今回の記事の経緯
今回はバックエンド側のGolangを用いたJWT認証のロジックを考えているときにぬまったお話をいたします。
The Clean Architectureに基づいてロジックを下記のように実装していました。
package controller
import (
"backend/model"
"backend/usecase"
"net/http"
"strconv"
"github.com/golang-jwt/jwt/v4"
"github.com/labstack/echo/v4"
)
type IHistoryController interface {
GetAllHistory(c echo.Context) error
// 以下省略
}
type historyController struct {
hu usecase.IHistoryUsecase
}
func NewHistoryController(hu usecase.IHistoryUsecase) IHistoryController {
return &historyController{hu}
}
func (hc *historyController) GetAllHistory(c echo.Context) error {
// jwtTokenに型アサーション
player := c.Get("player").(*jwt.Token)
claims := player.Claims.(jwt.MapClaims)
playerId := claims["player_id"]
historyRes, err := hc.hu.GetAllHistory(uint(playerId.(float64)))
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, historyRes)
}
// 以下省略~~~~~~~~~~~~~~~~~~~~~~
Model内は、下記のようにしておりました。
package model
import "time"
type History struct {
ID uint `json:"id" gorm:"primaryKey"`
Win uint `json:"win"`
Lose uint `json:"lose"`
Money uint `json:"money"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Player Player `json:"player" gorm:"foreignKey:PlayerId; constraint:OnDelete:CASCADE"`
PlayerId uint `json:"player_id" gorm:"not null"`
}
// 以下省略
ぬまった箇所
上記のコード内で,
player := c.Get("player").(*jwt.Token)
の箇所で私はGet()に関してちゃんと理解していなかったためによりぬまってしまいました。。。。
package router
import (
"backend/controller"
"os"
echojwt "github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/v4"
)
func NewRouter(uc controller.IPlayerController, hc controller.IHistoryController) *echo.Echo {
e := echo.New()
e.POST("/signup", uc.SignUp)
e.POST("/login", uc.Login)
e.POST("/logout", uc.Logout)
h := e.Group("/history")
// middlewareを追加
h.Use(echojwt.WithConfig(echojwt.Config{
// jwtを生成した時と同じSECRETkeyを指定
SigningKey: []byte(os.Getenv("SECRET")),
// clientから送られてくるjwtTokenがどこに格納されているかを示す
TokenLookup: "cookie:token",
}))
h.GET("", hc.GetAllHistory)
h.GET("/:historyId", hc.GetPlayerById)
h.POST("", hc.CreateHistory)
h.PUT("/:historyId", hc.UpdateHistoryByWinAndLose)
h.PUT("/:playerId", hc.UpdateHistoryByMoney)
return e
}
router側で上記のように実装して、Postmanで動作確認してみると、、、、
Error: socket hang up
が出ちゃいました。。。
当初は何でか分からず、迷走中。。。
そして、debug中にあることに気が付く。
そう。player変数にデータが格納されていませんでした。。。
- そもそもこのGetは何をするものなのか。
これは簡単。連想配列と同じ考え方でOK?なのかもしれない。
"key"に対応する"value"を取得する。
では、どのようにしたら、今回のように(*jwt.Token)に型アサーションして、playerに格納できるようになるのか。
解決策
Get("player")ではなく、Get("user")にする。
そうすると、、
と
Status: 200 OKが出ました。
また、基本ドキュメントを見ても、ここは基本Get("user")にしている。
では、この"user"はどこから出てきたの。。。。
答えはここにありました。
c.Get() is used to retrieve value from the current HTTP request context.
basically the JWT middleware that you are using intercept the HTTP request, validate the JWT token (by reading the HTTP Authorization header and checking token signature) and if everything's fine the middleware store the token information in the echo context for your later use.
The context has a map inside it to store variables, the JWT token is set using the user key, so that you can use the key to retrieve the token from it later on.
要するに、
コンテキストの内部には、変数を保存するためのマップがあり、JWT トークンは"user"キーを使用して設定されるため、後でそのキーを使用してそこからトークンを取得できるみたいです。
結論
jwtTokenに型アサーションする場合のGet()内では、おとなしく、"user"を使いましょう。
参考文献