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 实现登录验证_第1张图片

测试token数据合法性
(localhost:8080/user/list).token放到header里面

golang 中使用 JWT 实现登录验证_第2张图片

里面需要密码未加密需要优化,也欢迎其他能人志士指出其他错误,非常感谢,一起成长!!
有问题,可以加微信:cfun666,进go语言学习交流群

你可能感兴趣的:(golang)