gin +jwt实战 | 青训营笔记

396 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 10 天

(内容根据字节跳动青训营课程内容以及自己的理解编写)

近期将日更这几个主题的文章,欢迎关注!

  • Kitex
  • Hertx
  • go的测试环节
  • goFrame
  • Hertz中间件
  • Hertz路由

gin快速上手

1.第一步还是先安装依赖

go get -u github.com/gin-gonic/gin

image.png

2.写个简单示例

package main

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

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

3.启动

go run example.go

如果需要一些http常用的常量,比如状态码,还可以在net/http中找到诸如http.StatusOk之类的常量

结合jwt

1.安装依赖

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

image.png

2.定义JwtUtil工具

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 字符串

3.解析的方法

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 解析失败
  • 解析失败会返回对应错误信息

4.获取token

我们一般都是从Header中获取Authorization字段,然后解析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]
}

5.Gin中使用

在中间件中使用

官网对于中间件的示例

func Logger() gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()

		// 设置 example 变量
		c.Set("example", "12345")

		// 请求前

		c.Next()  // 这里我们token检验不合格的就不会通过了

		// 请求后
		latency := time.Since(t)
		log.Print(latency)

		// 获取发送的 status
		status := c.Writer.Status()
		log.Println(status)
	}
}

func main() {
	r := gin.New()
	r.Use(Logger())

	r.GET("/test", func(c *gin.Context) {
		example := c.MustGet("example").(string)

		// 打印:"12345"
		log.Println(example)
	})

	// 监听并在 0.0.0.0:8080 上启动服务
	r.Run(":8080")
}

模仿官方样例写一个 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 载荷payload中
  • 通过解析 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)
	}

        // todo 后续的处理逻辑
}

资料

gin中间件官方文档