这是我参与「第五届青训营 」伴学笔记创作活动的第 12 天
青训营的大项目中需要使用Go语言编写一个使用token方式来鉴权的服务端,所以使用go来实现jwt鉴权功能。
经过初步的搜索,现在已经有了很多go的jwt鉴权的实现
其中不仅有gin框架的,还有hertz框架的一些已经封装好的库。
因为我们的项目使用的是Hertz,所以使用 github.com/hertz-contr… 的封装来直接使用封装好的接口来进行jwt鉴权。
安装
go get github.com/hertz-contrib/jwt
安装完成后我们先查看readme中的demo,可以看到main函数中,最主要的实现了jwt鉴权部分的中间件,就是下面节选自demo的一部分。
func main() {
authMiddleware, err := jwt.New(&jwt.HertzJWTMiddleware{
Realm: "test zone",
Key: []byte("secret key"),
Timeout: time.Hour,
MaxRefresh: time.Hour,
IdentityKey: identityKey,
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(*User); ok {
return jwt.MapClaims{
identityKey: v.UserName,
}
}
return jwt.MapClaims{}
},
IdentityHandler: func(ctx context.Context, c *app.RequestContext) interface{} {
claims := jwt.ExtractClaims(ctx, c)
return &User{
UserName: claims[identityKey].(string),
}
},
Authenticator: func(ctx context.Context, c *app.RequestContext) (interface{}, error) {
var loginVals login
if err := c.BindAndValidate(&loginVals); err != nil {
return "", jwt.ErrMissingLoginValues
}
userID := loginVals.Username
password := loginVals.Password
if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") {
return &User{
UserName: userID,
LastName: "Hertz",
FirstName: "CloudWeGo",
}, nil
}
return nil, jwt.ErrFailedAuthentication
},
Authorizator: func(data interface{}, ctx context.Context, c *app.RequestContext) bool {
if v, ok := data.(*User); ok && v.UserName == "admin" {
return true
}
return false
},
Unauthorized: func(ctx context.Context, c *app.RequestContext, code int, message string) {
c.JSON(code, map[string]interface{}{
"code": code,
"message": message,
})
},
// TokenLookup is a string in the form of "<source>:<name>" that is used
// to extract token from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>"
// - "query:<name>"
// - "cookie:<name>"
// - "param:<name>"
TokenLookup: "header: Authorization, query: token, cookie: jwt",
// TokenLookup: "query:token",
// TokenLookup: "cookie:token",
// TokenHeadName is a string in the header. Default value is "Bearer". If you want empty value, use WithoutDefaultTokenHeadName.
TokenHeadName: "Bearer",
// TimeFunc provides the current time. You can override it to use another time value. This is useful for testing or if your server uses a different time zone than your tokens.
TimeFunc: time.Now,
})
}
jwt.HertzJWTMiddleware这个结构中,包含了一些进行jwt集成最主要的部分,下面根据我对它一一解释。
Realm
填写自定义领域名,用来展示给用户
Key
填写签名所需的密钥,每个应用都需要唯一,防止泄露
Timeout
token的过期时间
MaxRefresh
定义客户端多少时间后允许刷新密钥
TokenLookup
从http请求的哪里查token,例如
header: Authorization, query: token, cookie: jwt, form: token
就可以从header的Authorization,query的token,cookie的jwt,form的token来查找token。
TokenHeadName
填写token如果在http请求头中,前缀是什么。一般情况下都是Bearer
Authenticator
提供一个函数
func(ctx context.Context, c *app.RequestContext) (interface{}, error)
这个函数会在用户登录的时候被调用,通过这个函数获取context中的用户名密码或者其他登录信息,判断是否登录成功,并返回用户对象数据。后面PayloadFunc会根据这个数据来生成token。
这个函数需要额外被登录路由注册。
LoginResponse
提供一个函数
func(ctx context.Context, c *app.RequestContext, code int, token string, expire time.Time)
通过这个函数,返回用户的登录请求。在Authenticator登录成功之后触发。
IdentityKey
设置后面鉴权的token会被设置到hertz的context中的哪个key中,建议使用一个常量
IdentityHandler
提供一个函数
func(ctx context.Context, c *app.RequestContext) interface{}
从context中获取指定的信息,通过数据库查询用户实体信息,返回一个用户对象。
这个对象后续可以通过*app.RequestContext.Get(IdentityKey)得到。
PayloadFunc
在Authenticator不返回错误后被调用,提供一个函数
func(data interface{}) jwt.MapClaims
在函数内对jwt.MapClaims进行封装,填写进去你需要的数据。这里的数据会在以后被生成到token的payload中。
HTTPStatusMessageFunc
提供一个函数
func(e error, ctx context.Context, c *app.RequestContext) string
用来处理http请求错误的情况。
Unauthorized
提供一个函数
func(ctx context.Context, c *app.RequestContext, code int, message string)
对未鉴权或者鉴权失败的请求进行返回消息。
router注册
后续只需要在登录路由时注册 JwtMiddleware.LoginHandler
并在每一个需要鉴权的路由,添加中间件 JwtMiddleware.MiddlewareFunc()
即可完成鉴权。Token的相关信息可以通过context中获取key为IdentityKey来得到。