抖声后端开发记录(4)| 青训营笔记

189 阅读2分钟

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

中间件

准备加入JWT作为token。

创建我们自己需要的Claim。我们自己的字段加上标准的JWT字段。

type Claim struct {
	Session int64 `json:"session"`
	User    int64 `json:"user"`
	jwt.RegisteredClaims
}

JWT生成方式需要用到特定的成员函数,所以需要使用组合的方式将这个RegisteredClaim合到自己的结构体,不然用不了。这个标准结构里面有很多字段可以直接拿过来用。

jwt用到了加密所以需要一个key。

var key []byte

func ReflashKey() {
	rand.Read(key)
}

预备部分如下:

var (
	synID    int64 = 0
	key      []byte
	keyOnce  sync.Once
	instance JWT
)

type Claim struct {
	Session int64 `json:"session"`
	User    int64 `json:"user"`
	jwt.RegisteredClaims
}

type JWT struct{}

func NewInstance() *JWT {
	keyOnce.Do(func() {
		key = make([]byte, 4)
		rand.Read(key)
		instance = JWT{}
	})
	return &instance
}

// flash the key will make token invaild which created before
func ReflashKey() {
	rand.Read(key)
}

接下来我们来写生成token的接口。这里我们只需要一个userid来标志这个token的所有者。其实原本想直接用aud这个字段来的,但是新版的jwt里面将这些字段类型都做了封装,导致操作起来挺麻烦的,就放弃了。

func (j *JWT) GenToken(userid int64, exp int64) (string, error) {
	atomic.AddInt64(&synID, 1)
	t := time.Now()
	claim := Claim{
		Session: synID,
		User:    userid,
		RegisteredClaims: jwt.RegisteredClaims{
			IssuedAt:  jwt.NewNumericDate(t),
			ExpiresAt: jwt.NewNumericDate(t.Add(time.Duration(exp) * time.Second)),
		},
	}
	return jwt.NewWithClaims(jwt.SigningMethodHS256, claim).SignedString(key)
}

原本用到额是StandardClaim类型,结果提示被弃用了。改为RegisteredClaim,但是这个新类型的字段全是封装类型,真麻烦。

接下来是解析:

func (j *JWT) ParseToken(tk string) (*jwt.Token, error) {
	result, err := jwt.ParseWithClaims(tk, &Claim{}, func(t *jwt.Token) (interface{}, error) {
		return key, nil
	})
	return result, err
}

这个jwt的解析函数说实话我以前是没有见过这种类型的。但分析一下好像也没啥,主要是别人的样例代码写的都是内联函数导致看起来好高级。

这个keyfunc实际上就是密钥生成函数。这里单独出来是可以将密钥部分从jwt独立出去。这个函数我们可以调用其他模组的函数。这样可以做到一个封装和解耦。

随后可以开始写我们的中间件了。

func Verify(c *gin.Context) {

	// should we create two func, one for Query and one for PostForm?
	var tk string
	if tk = c.PostForm("token"); tk == "" {
		if tk = c.Query("token"); tk == "" {
			c.Set("Visitor", true)
			return // no token provide, this mean this is a visitor
		}
	}
	result, _ := NewInstance().ParseToken(tk)

	if !result.Valid {
		c.AbortWithStatus(http.StatusForbidden) // invaild token

		log.Printf("[WARN] Invaild JWT : %s", tk)

		return
	}

	c.Set("Visitor", false)
	c.Set("UserID", result.Claims.(*Claim).User)

}

我这个中间件的逻辑其实很简单。也就是拿token然后看有没有过期或者被篡改。这部分校验是由jwt完成的。

然后在上下文中加入这个token的userid。 如果没有提供token的话,会在上下文添加Visitor属性。表示这个请求是由未登录的用户发送的。