Gin 集成 jwt-go 验证授权 | Go 主题月

1,674

使用 gin web 框架,在 moose-go 基础之上开发

安装

go get -u github.com/dgrijalva/jwt-go

封装 JWT

生成 JWT

type JwtUtil struct{}

var verifyKey = []byte("moose-go")

type CustomClaims struct {
	*jwt.StandardClaims
	*model.UserInfo
}

func GeneratorJwt(userInfo *model.UserInfo) (string, error) {
	claims := &CustomClaims{
		&jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Minute * 60 * 24).Unix(),
			IssuedAt:  time.Now().Unix(),
		},
		userInfo,
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(verifyKey)
}

  • jwt.StandardClaims 负载 jwt 信息
    • ExpiresAt 过期时间
    • IssuedAt 签发时间
  • 创建 CustomClaims 结构体,用来封装 jwt 信息
  • jwt.NewWithClaims 创建 jwt
    • jwt.SigningMethodHS256 使用 SigningMethodHS256 签名
    • claims 负载信息
  • token.SignedString 转换成 str 字符串

解析 JWT

func ParseJwt(tokenStr string) *jwt.Token {
	token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return verifyKey, nil
	})

	if token.Valid {
		log.Println("You look nice today")
		return token
	} else if ve, ok := err.(*jwt.ValidationError); ok {
		if ve.Errors&jwt.ValidationErrorMalformed != 0 {
			log.Printf("%v", ve)
			panic(api.JwtValidationErr)
		} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
			log.Printf("%v", ve)
			panic(api.JwtExpiresErr)
		} else {
			panic(api.JwtExpiresErr)
		}
	} else {
		panic(api.JwtExpiresErr)
	}
}

  • jwt.Parse 解析 jwt,需要保持签名一直 verifyKey
  • token.Valid 返回 bool 值,true 解析成功,false 解析失败
  • 解析失败会返回对应错误信息

从 Header 中解析、获取 token

func ParseBearerToken(token string) string {
	tokens := strings.Split(token, " ")
	if len(tokens) != 2 || !strings.EqualFold("Bearer", tokens[0]) {
		panic(api.JwtValidationErr)
	}
	return tokens[1]
}

Gin 中使用

在中间件中使用

auth_required.go

package middleware

import (
	"log"
	"moose-go/api"
	"moose-go/util"

	"github.com/gin-gonic/gin"
)

var anonymous = []string{
	"/api/v1/account/register",
	"/api/v1/account/login",
	"/api/v1/qrcode/get",
	"/api/v1/qrcode/ask",
	"/api/v1/sms/send",
	"/api/v1/user/info",
}

func AuthRequired() gin.HandlerFunc {
	return func(c *gin.Context) {
		path := c.Request.URL.Path
		if util.In(path, anonymous) {
			c.Next()
			return
		}

		bearerToken := c.GetHeader("Authorization")
		log.Println("auth check token... ", bearerToken)

		if bearerToken == "" {
			panic(api.JwtValidationErr)
		}

		// Bearer xxxx
		token := util.ParseBearerToken(bearerToken)
		util.ParseJwt(token)
		c.Next()
	}
}

  • anonymous 可以匿名访问,不需要登录的接口

  • path := c.Request.URL.Path 获取访问路由

  • if util.In(path, anonymous) 在匿名访问中,直接放行 c.Next()

  • bearerToken := c.GetHeader("Authorization") 从头部获取 token

  • 解析 token,校验 token

    token := util.ParseBearerToken(bearerToken)
    util.ParseJwt(token)
    

登录接口

  • 在登录校验通过之后,生成 jwt token 返回
  • 把用户 id 封装到 jwt 负载中
  • 通过解析 jwt 获取 userId,通过 userId 获取用户信息
userId := string(userResult[0]["user_id"])
return createToken(&model.UserInfo{UserId: userId})
// cerate jwt tokn
func createToken(userInfo *model.UserInfo) string {
	token, err := util.GeneratorJwt(userInfo)
	if err != nil {
		panic(api.JwtGeneratorErr)
	}
	return token
}

获取用户信息

func (uc *UserService) GetUserByToken(header string) *model.UserInfo {
	token := util.ParseBearerToken(header)
	jwtToken := util.ParseJwt(token)
	data, err := json.Marshal(jwtToken.Claims)
	if err != nil {
		panic(api.QueryUserFailErr)
	}

	var claims jwt.MapClaims
	err = json.Unmarshal(data, &claims)
	if err != nil {
		panic(api.QueryUserFailErr)
	}

	userId, ok := claims["userId"]
	if userId == "" || !ok {
		panic(api.QueryUserFailErr)
	}

    。。。。
}

测试

用例

查看 gitee.com/shizidada/m…

登录

image-20210407144634647

使用 token 获取用户信息

image-20210407144835629