golang 中使用 JWT 实现登录验证

简介

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
讨论数量: 3

刷新token之后,原token还有用吧?假如原token没有被销毁的话,是不是应该也还是可以继续使用的?

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!