golang 中使用 JWT 實現登入驗證

程式碼的壞味道發表於2020-01-07

簡介

JWT是json web token
具體jwt的組成,加密方式等等自行百度解決,我這裡僅寫實現案例:

控制器程式碼

package controller

import (
    "errors"
    "fmt"
    "gindemo/dto"
    "gindemo/middleware"
    "gindemo/middleware/jwt"
    "gindemo/models"
    "github.com/gin-gonic/gin"
    "log"
)

func Login(c *gin.Context) {
    loginInput := &dto.LoginInput{}//我這裡分層了,主要是把引數驗證這塊單獨分離出來了
    if err := loginInput.BindingValidParams(c); err != nil {
        middleware.ResponseError(c, 2001, err)
        return
    }
    user := &models.User{}
    fmt.Println(loginInput)
    token, err := user.Login(loginInput.UserName, loginInput.Password)
    if err != nil {
        if err.Error() == "record not found" {
            middleware.ResponseError(c, 500, errors.New("該使用者不存在"))
            return
        } else {
            middleware.ResponseError(c, 500, errors.New("登入錯誤"))
            return
        }
    }
    middleware.ResponseSuccess(c,token)
    return
}

//用於測試使用
func UserList(c *gin.Context) {
    var user models.User
    claims := c.MustGet("claims").(*jwt.CustomClaims)
    users, err := user.ListUsers(claims.Name)
    if err != nil {
        log.Fatal(err)
    }
    middleware.ResponseSuccess(c, users)
}

引數驗證

package dto

import (
    "errors"
    "gindemo/public"
    "github.com/gin-gonic/gin"
    ut "github.com/go-playground/universal-translator"
    "gopkg.in/go-playground/validator.v9"
    zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
    "strings"
)

type LoginInput struct {
    UserName string `form:"username" validate:"required"`
    Password string `form:"password" validate:"required"`
}

func (o *LoginInput) BindingValidParams(c *gin.Context) error {
    //繫結資料
    if err := c.ShouldBind(o); err != nil {
        return err
    }
    v := c.Value("trans")
    trans, ok := v.(ut.Translator)
    if !ok {
        trans, _ = public.Uni.GetTranslator("zh")
    }
    ////驗證器註冊翻譯器
    //e := zh_translations.RegisterDefaultTranslations(public.Validate, trans)
    //if e != nil {
    //    return e
    //}
    //驗證
    err := public.Validate.Struct(o)
    if err != nil {
        sliceErrs := []string{}
        for _, e := range err.(validator.ValidationErrors) {

            sliceErrs = append(sliceErrs, e.Translate(trans))
        }
        return errors.New(strings.Join(sliceErrs, ","))
    }
    return nil
}

生成token

package models

import (
   "errors"
 "fmt" "gindemo/database" "gindemo/middleware/jwt"  jwtgo "github.com/dgrijalva/jwt-go" 
  "github.com/jinzhu/gorm"
 "log" "time")

type User struct {
   Id int `form:"id" json:"id" gorm:"PRIMARY_KEY"`
  Name string `form:"username" json:"username"`
  Email string `form:"email" json:"email",binding:"required"`
  Password string `form:"password" json:"-",binding:"required"`
}

type LoginResult struct {
   User interface{} `json:"user"`
  Token string `json:"token"`
}

func (User) TableName() string {
   return "users"
}

func (u *User) Login(name string, password string) (token LoginResult, err error) {
   var user User
  fmt.Println(name, password)
   obj := database.GormPool.Where("name = ? and password=?", name, password).First(&user)
   if err = obj.Error; err != nil {
      return
  }
   generateToken := GenerateToken(user)
   return generateToken, nil
}

//測試使用
func (u *User) ListUsers(name string) (users []User, err error) {
   query := database.GormPool
  if name != "" {
      query = query.Where("name=?", name)
   }
   err = query.Find(&users).Error
  if err != nil && err != gorm.ErrRecordNotFound {
      return nil, err
  }
   return
}

// 生成令牌  建立jwt風格的token
func GenerateToken(user User) LoginResult {
   j := &jwt.JWT{
      []byte("newtrekWang"),
   }
   claims := jwt.CustomClaims{
      user.Id,
      user.Name,
      user.Password,
      jwtgo.StandardClaims{
         NotBefore: int64(time.Now().Unix() - 1000), // 簽名生效時間
  ExpiresAt: int64(time.Now().Unix() + 3600), // 過期時間 一小時
  Issuer:    "cfun",                   //簽名的發行者
  },
   }

   token, err := j.CreateToken(claims)
   if err != nil {
      return LoginResult{
         User:  user,
         Token: token,
      }
   }

   log.Println(token)
   data := LoginResult{
      User:  user,
      Token: token,
   }
   return data
}

建立jwt.go檔案

package jwt

import (
    "errors"
    "fmt"
    "gindemo/middleware"
    "github.com/dgrijalva/jwt-go"
    "github.com/gin-gonic/gin"
    "log"
    "time"
)

// JWTAuth 中介軟體,檢查token
func JWTAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.Request.Header.Get("token")
        if token == "" {
            middleware.ResponseError(c, -1, errors.New("請求未攜帶token,無許可權訪問"))
            c.Abort()
            return
        }
        log.Print("get token: ", token)
        j := NewJWT()
        // parseToken 解析token包含的資訊
        claims, err := j.ParseToken(token)
        fmt.Println("claims", claims)
        if err != nil {
            if err == TokenExpired {
                middleware.ResponseError(c, -1, errors.New("授權已過期"))
                c.Abort()
                return
            }
            middleware.ResponseError(c, -1, err)
            c.Abort()
            return
        }
        // 繼續交由下一個路由處理,並將解析出的資訊傳遞下去
        c.Set("claims", claims)
    }
}

// JWT 簽名結構
type JWT struct {
    SigningKey []byte
}

// 一些常量
var (
    TokenExpired     error  = errors.New("Token is expired")
    TokenNotValidYet error  = errors.New("Token not active yet")
    TokenMalformed   error  = errors.New("That's not even a token")
    TokenInvalid     error  = errors.New("Couldn't handle this token:")
    SignKey          string = "cfun"
)

// 載荷,可以加一些自己需要的資訊
type CustomClaims struct {
    ID       int    `json:"userId"`
    Name     string `json:"name"`
    Password string `json:"telephone"`
    jwt.StandardClaims
}

// 新建一個jwt例項
func NewJWT() *JWT {
    return &JWT{
        []byte(GetSignKey()),
    }
}

// 獲取signKey
func GetSignKey() string {
    return SignKey
}

// 這是SignKey
func SetSignKey(key string) string {
    SignKey = key
    return SignKey
}

// CreateToken 生成一個token
func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(j.SigningKey)
}

// 解析Tokne
func (j *JWT) ParseToken(tokenString string) (*CustomClaims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
        return j.SigningKey, nil
    })
    if err != nil {
        if ve, ok := err.(*jwt.ValidationError); ok {
            if ve.Errors&jwt.ValidationErrorMalformed != 0 {
                return nil, TokenMalformed
            } else if ve.Errors&jwt.ValidationErrorExpired != 0 {
                // Token is expired
                return nil, TokenExpired
            } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
                return nil, TokenNotValidYet
            } else {
                return nil, TokenInvalid
            }
        }
    }
    if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
        return claims, nil
    }
    return nil, TokenInvalid
}

// 更新token
func (j *JWT) RefreshToken(tokenString string) (string, error) {
    jwt.TimeFunc = func() time.Time {
        return time.Unix(0, 0)
    }
    token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
        return j.SigningKey, nil
    })
    if err != nil {
        return "", err
    }
    if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
        jwt.TimeFunc = time.Now
        claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
        return j.CreateToken(*claims)
    }
    return "", TokenInvalid
}

用到的公共返回方法

package middleware

import (
    "encoding/json"
    "github.com/gin-gonic/gin"
)

const (
    SuccessCode ResponseCode = 200
)

type ResponseCode int

type Response struct {
    ErrorCode ResponseCode `json:"errno"`
    ErrorMsg  string       `json:"errmsg"`
    Data      interface{}  `json:"data"`
}

func ResponseError(c *gin.Context, code ResponseCode, err error) {
    resp := &Response{ErrorCode: code, ErrorMsg: err.Error(), Data: ""}
    c.JSON(200, resp)
    response, _ := json.Marshal(resp)
    c.Set("response", string(response))
    //c.AbortWithError(200, err)
}

func ResponseSuccess(c *gin.Context, data interface{}) {
    resp := &Response{ErrorCode: SuccessCode, ErrorMsg: "", Data: data}
    c.JSON(200, resp)
    response, _ := json.Marshal(resp)
    c.Set("response", string(response))
}

路由定義

func InitRouter() *gin.Engine {
   file, _ := os.Create("logs/app/log")
   gin.DefaultWriter = io.MultiWriter(file)
   router := gin.Default()
   router.Use(gin.Recovery(), gin.Logger())
   //登入註冊
  router.POST("/login", controller.Login)
   router.POST("/register", controller.Register)
   //使用者相關
  userRoute := router.Group("user")
   userRoute.Use(jwt.JWTAuth())//這裡使用Use,jwtAuth就行
   userRoute.GET("/list", controller.UserList)
}

登入獲取token.
(localhost:8080/login?username=cfun&password=123456)

golang 中使用 JWT 實現登入驗證

測試token資料合法性
(localhost:8080/user/list).token放到header裡面

golang 中使用 JWT 實現登入驗證

裡面需要密碼未加密需要最佳化,也歡迎同學們指出其他錯誤,非常感謝,一起成長!!

本作品採用《CC 協議》,轉載必須註明作者和本文連結
cfun

相關文章