这是我参与「第五届青训营 」伴学笔记创作活动的第 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属性。表示这个请求是由未登录的用户发送的。