やりたいこと
Ginでユーザーログイン機能を実装し、ログインユーザーだけがアクセス可能なエンドポイントをJWT認証を使って実装したい。
JWT認証の仕組み
JWTとは
JWT認証の仕組み
Ginで実装してみる
main.go
main.go
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
func main() {
r := gin.Default()
r.POST("/login", loginHandler)
authGroup := r.Group("/auth")
authGroup.Use(authMiddleware)
authGroup.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "you are authorized"})
})
r.Run(":8080")
}
-
POST
/login
:ログイン時にアクセスするエンドポイント -
GET
/auth
:ログインユーザーしかアクセスできない
/auth
が付くエンドポイントをグループ化し、middlewareを用いて、JWT認証できたユーザーのみアクセスできるようにする。
User
構造体を定義
type User struct {
Username string `json:"username"`
Password string `json:"password"`
}
var validUser = User{
Username: "test",
Password: "testpass",
}
loginHandler
func loginHandler(c *gin.Context) {
var inputUser User
// リクエストからユーザー情報を取得
if err := c.BindJSON(&inputUser); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// ユーザー情報の検証
if inputUser.Username != validUser.Username || inputUser.Password != validUser.Password {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user"})
return
}
// トークンの発行(ヘッダー・ペイロード)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": inputUser.Username,
"exp": time.Now().Add(time.Hour * 24).Unix(),
})
tokenString, err := token.SignedString([]byte(SECRET_KEY))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "error signing token"})
return
}
// ヘッダーにトークンをセット
c.Header("Authorization", tokenString)
c.JSON(http.StatusOK, gin.H{"message": "login success"})
}
リクエストBodyからUser
構造体へバインドし、Username
とPassword
が一致する登録ユーザーが存在するかを検証します。今回は面倒なのでDBを使わずグローバル変数で定義しています。
その後JWTトークンを発行し、HTTP HeaderのAuthorization
にセットしてレスポンスを返します。
const SECRET_KEY = "SECRET"
署名に使う秘密鍵はこんな感じで定義しておきます。
authMiddleware
func authMiddleware(c *gin.Context) {
// Authorizationヘッダーからトークンを取得
tokenString := c.GetHeader("Authorization")
// トークンの検証
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(SECRET_KEY), nil
})
if err != nil || !token.Valid {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
c.Abort()
return
}
c.Next()
}
リクエストのHTTP HeaderのAuthorization
からJWTトークンを取得します。
その後、jwt.Parse
を用いてJWTトークンを検証します。ここのミソはDBにアクセスしてユーザー情報を照合する必要がないことだと思います。JWTのシグネチャをデコードしてペイロードと一致すれば有効なトークンであることが保証されているのが理由です。
参考