Usare JWT in Gin Gonic
In questo articolo vediamo come usare JWT in Gin Gonic.
Oltre alla libreria apposita, io ho usato anche cors e gorm.
Per quanto riguarda JWT, installiamo il package così:
$ go get -u github.com/golang-jwt/jwt/v5
$ go mod tidy
Fatto questo cominciamo dal model per la tabella utenti:
package models
import "time"
type Utente struct {
ID int32 `json:"ut_id" gorm:"column:ut_id;primaryKey"`
User string `json:"ut_user" gorm:"column:ut_user;unique"`
Password string `json:"ut_password" gorm:"column:ut_password"`
DataReg time.Time `json:"ut_data_reg" gorm:"column:ut_data_reg"`
DataLogin time.Time `json:"ut_data_login" gorm:"column:ut_data_login"`
DataModifica time.Time `json:"ut_data_modifica" gorm:"column:ut_data_modifica"`
TokenJwt string `json:"ut_token"`
}
func (Utente) TableName() string {
return "todo_utenti"
}
Nel mio caso ho dovuto "forzare" il nome della tabella, in quanto già esistente e non creata con gorm.
Questo il controller con la funzione per il login:
package controllers
import (
"net/http"
"os"
"strings"
"time"
"todo/models"
"todo/utils"
"github.com/MakeNowJust/heredoc"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
)
func Login(ctx *gin.Context) {
var item models.Utente
var response utils.CustomResponse
if err := ctx.BindJSON(&item); err != nil {
response.Res = "ko"
response.Message = "Si è verificato un errore!"
response.Error = err.Error()
response.Last = 0
ctx.JSON(http.StatusOK, gin.H{"data": response})
ctx.Done()
} else {
var user string = strings.TrimSpace(strings.ToLower(item.User))
var password string = item.Password
models.DB.Where("ut_user = ?", user).Where("ut_password = ?", password).First(&item)
if item.ID > 0 {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"exp": time.Now().AddDate(1, 0, 0).Unix(),
"sub": item.ID,
})
tokenString, err := token.SignedString([]byte(os.Getenv("SECRET")))
if err != nil {
response.Res = "ko"
response.Message = "Si è verificato un errore!"
response.Error = err.Error()
response.Last = 0
ctx.JSON(http.StatusOK, gin.H{"data": response})
ctx.Done()
}
item.TokenJwt = tokenString
strQuery := heredoc.Doc(`
UPDATE todo_utenti
SET ut_data_login = ?
WHERE ut_user = ?
`)
models.DB.Exec(strQuery, time.Now(), item.ID)
ctx.JSON(http.StatusOK, gin.H{"data": item})
ctx.Done()
} else {
response.Res = "ko"
response.Message = "Si è verificato un errore!"
response.Error = "Credenziali errate!"
response.Last = 0
ctx.JSON(http.StatusOK, gin.H{"data": response})
ctx.Done()
}
}
}
Nel mio caso c'è anche l'update della data per ultimo login; magari a voi non serve.
Questo il middleware:
package middleware
import (
"errors"
"net/http"
"os"
"strings"
"time"
"todo/models"
"todo/utils"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
)
func CheckToken(ctx *gin.Context) {
tokenString, err := extractBearerToken(ctx.GetHeader("Authorization"))
var response utils.CustomResponse
if err != nil {
response.Res = "ko"
response.Message = "Attenzione!"
response.Error = "Non sei autorizzato"
response.Last = 0
ctx.JSON(http.StatusOK, gin.H{"data": response})
ctx.Done()
}
token, err := parseToken(tokenString)
if err != nil {
response.Res = "ko"
response.Message = "Attenzione!"
response.Error = "Non sei autorizzato"
response.Last = 0
ctx.JSON(http.StatusOK, gin.H{"data": response})
ctx.Done()
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
response.Res = "ko"
response.Message = "Attenzione!"
response.Error = "Non sei autorizzato"
response.Last = 0
ctx.JSON(http.StatusOK, gin.H{"data": response})
ctx.Done()
}
if float64(time.Now().Unix()) > claims["exp"].(float64) {
response.Res = "ko"
response.Message = "Attenzione!"
response.Error = "Non sei autorizzato"
response.Last = 0
ctx.JSON(http.StatusOK, gin.H{"data": response})
ctx.Done()
}
var user models.Utente
models.DB.First(&user, claims["sub"])
if user.ID == 0 {
response.Res = "ko"
response.Message = "Attenzione!"
response.Error = "Non sei autorizzato"
response.Last = 0
ctx.JSON(http.StatusOK, gin.H{"data": response})
ctx.Done()
}
ctx.Set("user", user)
ctx.Next()
}
func extractBearerToken(header string) (string, error) {
if header == "" {
return "", errors.New("bad header value given")
}
jwtToken := strings.Split(header, " ")
if len(jwtToken) != 2 {
return "", errors.New("incorrectly formatted authorization header")
}
return jwtToken[1], nil
}
func parseToken(jwtToken string) (*jwt.Token, error) {
token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) {
if _, OK := token.Method.(*jwt.SigningMethodHMAC); !OK {
return nil, errors.New("bad signed method received")
}
return []byte(os.Getenv("SECRET")), nil
})
if err != nil {
return nil, errors.New("bad jwt token")
}
return token, nil
}
La funzione CheckToken è quella che verrà richiamata dalle rotte.
Il SECRET lo impostiamo nel main, ma sarebbe meglio metterlo in un file:
package main
import (
"fmt"
"os"
"todo/controllers"
"todo/middleware"
"todo/models"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
os.Setenv("SECRET", "SECRET")
router := gin.Default()
router.SetTrustedProxies(nil)
config := cors.DefaultConfig()
config.AllowAllOrigins = true
config.AllowMethods = []string{"POST", "GET", "PUT", "OPTIONS"}
config.AllowHeaders = []string{"Origin", "Content-Type", "Authorization", "Accept", "User-Agent", "Cache-Control", "Pragma"}
config.ExposeHeaders = []string{"Content-Length"}
config.AllowCredentials = true
router.Use(cors.New(config))
models.ConnectDatabase()
router.GET("/", func(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"message": "Beviti un GIN!",
})
})
clientiRoute := router.Group("clienti")
{
clientiRoute.GET("/", middleware.CheckToken, controllers.GetClienti)
}
utentiRoute := router.Group("utenti")
{
utentiRoute.POST("login", controllers.Login)
utentiRoute.POST("register", controllers.Register)
}
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
fmt.Println("Server in asolto su http://localhost:" + port)
if err := router.Run(":" + port); err != nil {
os.Exit(1)
}
}
Per completezza vi metto qua sotto anche CustomReponse che avete in giro:
package utils
type CustomResponse struct {
Res string `json:"res"`
Message string `json:"message"`
Error string `json:"error"`
Last int32 `json:"last"`
}
ConnectDatabase invece inizializza la connessione:
package models
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
func ConnectDatabase() {
dsn := "........"
database, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println(err)
}
DB = database
}
Direi che è tutto!
Enjoy!
go gin gonic gorm jwt cors
Commentami!