Go实现JWT | 青训营笔记

188 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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来得到。