前言

上一篇文章简单介绍了一个高性能的 Go HTTP 框架——Hertz,本篇文章将围绕 Hertz 开源仓库的一个 demo,讲述如何使用 Hertz 完成 JWT 的认证与授权流程。

这里要说明的是,hertz-jwt 是 Hertz 众多外部扩展组件之一,Hertz 丰富的扩展生态为开发者带来了很大的便利,值得你在本文之外自行探索。

Demo 介绍

  • 使用命令行工具 hz 生成代码
  • 使用 JWT 扩展完成登陆认证和授权访问
  • 使用 Gorm 访问 MySQL 数据库

Demo 下载

git clone https://github.com/cloudwego/hertz-examples.git
cd bizdemo/hertz_jwt

Demo 结构

hertz_jwt
├── Makefile # 使用 hz 命令行工具生成 hertz 脚手架代码
├── biz
│   ├── dal
│   │   ├── init.go
│   │   └── mysql
│   │       ├── init.go # 初始化数据库连接
│   │       └── user.go # 数据库操作
│   ├── handler
│   │   ├── ping.go
│   │   └── register.go # 用户注册 handler
│   ├── model
│   │   ├── sql
│   │   │   └── user.sql
│   │   └── user.go # 定义数据库模型
│   ├── mw
│   │   └── jwt.go # 初始化 hertz-jwt 中间件
│   ├── router
│   │   └── register.go
│   └── utils
│       └── md5.go # md5 加密
├── docker-compose.yml # mysql 容器环境支持
├── go.mod
├── go.sum
├── main.go # hertz 服务入口
├── readme.md
├── router.go # 路由注册
└── router_gen.go

Demo 分析

下方是这个 demo 的接口列表。

// customizeRegister registers customize routers.
func customizedRegister(r *server.Hertz) {
   r.POST("/register", handler.Register)
   r.POST("/login", mw.JwtMiddleware.LoginHandler)
   auth := r.Group("/auth", mw.JwtMiddleware.MiddlewareFunc())
   auth.GET("/ping", handler.Ping)
}

用户注册

对应 /register 接口,当前 demo 的用户数据通过 gorm 操作 mysql 完成持久化,因此在登陆之前,需要对用户进行注册,注册流程为:

  1. 获取用户名密码和邮箱
  2. 判断用户是否存在
  3. 创建用户

用户登陆(认证)

服务器需要在用户第一次登陆的时候,验证用户账号和密码,并签发 jwt token。

JwtMiddleware, err = jwt.New(&jwt.HertzJWTMiddleware{
   Key:           []byte("secret key"),
   Timeout:       time.Hour,
   MaxRefresh:    time.Hour,
   Authenticator: func(ctx context.Context, c *app.RequestContext) (interface{}, error) {
       var loginStruct struct {
           Account  string `form:"account" json:"account" query:"account" vd:"(len($) > 0 && len($) < 30); msg:'Illegal format'"`
           Password string `form:"password" json:"password" query:"password" vd:"(len($) > 0 && len($) < 30); msg:'Illegal format'"`
      }
       if err := c.BindAndValidate(&loginStruct); err != nil {
           return nil, err
      }
       users, err := mysql.CheckUser(loginStruct.Account, utils2.MD5(loginStruct.Password))
       if err != nil {
           return nil, err
      }
       if len(users) == 0 {
           return nil, errors.New("user already exists or wrong password")
      }

       return users[0], nil
  },
   PayloadFunc: func(data interface{}) jwt.MapClaims {
       if v, ok := data.(*model.User); ok {
           return jwt.MapClaims{
               jwt.IdentityKey: v,
          }
      }
       return jwt.MapClaims{}
  },
})
  • Authenticator:用于设置登录时认证用户信息的函数,demo 当中定义了一个 loginStruct 结构接收用户登陆信息,并进行认证有效性。这个函数的返回值 users[0] 将为后续生成 jwt token 提供 payload 数据源。
  • PayloadFunc:它的入参就是 Authenticator 的返回值,此时负责解析 users[0],并将用户名注入 token 的 payload 部分。
  • Key:指定了用于加密 jwt token 的密钥为 "secret key"
  • Timeout:指定了 token 有效期为一个小时。
  • MaxRefresh:用于设置最大 token 刷新时间,允许客户端在 TokenTime + MaxRefresh 内刷新 token 的有效时间,追加一个 Timeout 的时长。

Token 的返回

JwtMiddleware, err = jwt.New(&jwt.HertzJWTMiddleware{
   LoginResponse: func(ctx context.Context, c *app.RequestContext, code int, token string, expire time.Time) {
       c.JSON(http.StatusOK, utils.H{
           "code":    code,
           "token":   token,
           "expire":  expire.Format(time.RFC3339),
           "message": "success",
      })
  },
})
  • LoginResponse:在登陆成功之后,jwt token 信息会随响应返回,你可以自定义这部分的具体内容,但注意不要改动函数签名,因为它与 LoginHandler 是强绑定的。

Token 的校验

访问配置了 jwt 中间件的路由时,会经过 jwt token 的校验流程。

JwtMiddleware, err = jwt.New(&jwt.HertzJWTMiddleware{
   TokenLookup:   "header: Authorization, query: token, cookie: jwt",
   TokenHeadName: "Bearer",
   HTTPStatusMessageFunc: func(e error, ctx context.Context, c *app.RequestContext) string {
       hlog.CtxErrorf(ctx, "jwt biz err = %+v", e.Error())
       return e.Error()
  },
   Unauthorized: func(ctx context.Context, c *app.RequestContext, code int, message string) {
       c.JSON(http.StatusOK, utils.H{
           "code":    code,
           "message": message,
      })
  },
})
  • TokenLookup:用于设置 token 的获取源,可以选择 headerquerycookieparam,默认为 header:Authorization,同时存在是以左侧一个读取到的优先。当前 demo 将以 header 为数据源,因此在访问 /ping 接口时,需要你将 token 信息存放在 HTTP Header 当中。
  • TokenHeadName:用于设置从 header 中获取 token 时的前缀,默认为 "Bearer"
  • HTTPStatusMessageFunc:用于设置 jwt 校验流程发生错误时响应所包含的错误信息,你可以自行包装这些内容。
  • Unauthorized:用于设置 jwt 验证流程失败的响应函数,当前 demo 返回了错误码和错误信息。

用户信息的提取

JwtMiddleware, err = jwt.New(&jwt.HertzJWTMiddleware{
   IdentityKey: IdentityKey,
   IdentityHandler: func(ctx context.Context, c *app.RequestContext) interface{} {
       claims := jwt.ExtractClaims(ctx, c)
       return &model.User{
           UserName: claims[IdentityKey].(string),
      }
  },
})

// Ping .
func Ping(ctx context.Context, c *app.RequestContext) {
user, _ := c.Get(mw.IdentityKey)
c.JSON(200, utils.H{
"message": fmt.Sprintf("username:%v", user.(*model.User).UserName),
})
}
  • IdentityHandler:用于设置获取身份信息的函数,在 demo 中,此处提取 token 的负载,并配合 IdentityKey 将用户名存入上下文信息。
  • IdentityKey:用于设置检索身份的键,默认为 "identity"
  • Ping:构造响应结果,从上下文信息中取出用户名信息并返回。

其他组件

代码生成

上述代码大部分是通过 hz 命令行工具生成的脚手架代码,开发者无需花费大量时间在构建一个良好的代码结构上,专注于业务的编写即可。

hz new -mod github.com/cloudwego/hertz-examples/bizdemo/hertz_jwt

更进一步,在使用代码生成命令时,指定 IDL 文件,可以一并生成通信实体、路由注册代码。

示例代码(源自 hz 官方文档):

// idl/hello.thrift
namespace go hello.example struct HelloReq {
1: string Name (api.query="name"); // 添加 api 注解为方便进行参数绑定
} struct HelloResp {
1: string RespBody;
} service HelloService {
HelloResp HelloMethod(1: HelloReq request) (api.get="/hello");
} // 在 GOPATH 下执行
hz new -idl idl/hello.thrift

参数绑定

hertz 使用开源库 go-tagexpr 进行参数的绑定及验证,demo 中也频繁使用了这个特性。

var loginStruct struct {
// 通过声明 tag 进行参数绑定和验证
Account string `form:"account" json:"account" query:"account" vd:"(len($) > 0 && len($) < 30); msg:'Illegal format'"`
Password string `form:"password" json:"password" query:"password" vd:"(len($) > 0 && len($) < 30); msg:'Illegal format'"`
}
if err := c.BindAndValidate(&loginStruct); err != nil {
return nil, err
}

更多操作可以参考文档

Gorm

更多 Gorm 操作 MySQL 的信息可以参考 Gorm

Demo 运行

  • 运行 mysql docker 容器
cd bizdemo/hertz_jwt && docker-compose up
  • 创建 mysql 数据库

连接 mysql 之后,执行 user.sql

  • 运行 demo
cd bizdemo/hertz_jwt && go run main.go

API 请求

注册

# 请求
curl --location --request POST 'localhost:8888/register' \
--header 'Content-Type: application/json' \
--data-raw '{
"Username": "admin",
"Email": "admin@test.com",
"Password": "admin"
}'
# 响应
{
"code": 200,
"message": "success"
}

登陆

# 请求
curl --location --request POST 'localhost:8888/login' \
--header 'Content-Type: application/json' \
--data-raw '{
"Account": "admin",
"Password": "admin"
}'
# 响应
{
"code": 200,
"expire": "2022-11-16T11:05:24+08:00",
"message": "success",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Njg1Njc5MjQsImlkIjoyLCJvcmlnX2lhdCI6MTY2ODU2NDMyNH0.qzbDJLQv4se6dOHN51p21Rp3DjV1Lf131l_5k4cK6Wk"
}

授权访问 Ping

# 请求
curl --location --request GET 'localhost:8888/auth/ping' \
--header 'Authorization: Bearer ${token}'
# 响应
{
"message": "username:admin"
}

参考文献

使用 Go HTTP 框架 Hertz 进行 JWT 认证的更多相关文章

  1. drf框架中jwt认证,以及自定义jwt认证

    0909自我总结 drf框架中jwt 一.模块的安装 官方:http://getblimp.github.io/django-rest-framework-jwt/ 他是个第三方的开源项目 安装:pi ...

  2. drf认证组件、权限组件、jwt认证、签发、jwt框架使用

    目录 一.注册接口 urls.py views.py serializers.py 二.登录接口 三.用户中心接口(权限校验) urls.py views.py serializers.py 四.图书 ...

  3. drf认证组件(介绍)、权限组件(介绍)、jwt认证、签发、jwt框架使用

    目录 一.注册接口 urls.py views.py serializers.py 二.登录接口 三.用户中心接口(权限校验) urls.py views.py serializers.py 四.图书 ...

  4. DRF框架(七) ——三大认证组件之频率组件、jwt认证

    drf频率组件源码 1.APIView的dispatch方法的  self.initial(request,*args,**kwargs)  点进去 2.self.check_throttles(re ...

  5. drf框架 - JWT认证插件

    JWT认证 JWT认证方式与其他认证方式对比: 优点 1) 服务器不要存储token,token交给每一个客户端自己存储,服务器压力小 2)服务器存储的是 签发和校验token 两段算法,签发认证的效 ...

  6. NetCore+Dapper WebApi架构搭建(六):添加JWT认证

    WebApi必须保证安全,现在来添加JWT认证 1.打开appsettings.json添加JWT认证的配置信息 2.在项目根目录下新建一个Models文件夹,添加一个JwtSettings.cs的实 ...

  7. Django REST framework 之JWT认证

    Json Web Token 1.JWT简介 JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法.JWT 可以使用 H ...

  8. drf组件之jwt认证

    drf组件之jwt认证模块 一.认证规则 全称:json web token 解释:加密字符串的原始数据是json,后台产生,通过web传输给前台存储 格式:三段式 - 头.载荷.签名 - 头和载荷才 ...

  9. ASP.Net Core 3.0 中使用JWT认证

    JWT认证简单介绍     关于Jwt的介绍网上很多,此处不在赘述,我们主要看看jwt的结构.     JWT主要由三部分组成,如下: HEADER.PAYLOAD.SIGNATURE HEADER包 ...

  10. jwt 认证

    目录 jwt 认证示意图 jwt 认证算法:签发与检验 drf 项目的 jwt 认证开发流程(重点) drf-jwt 框架基本使用 token 刷新机制(了解) jwt 认证示意图 jwt 优势 1 ...

随机推荐

  1. Can't pickle local object '_createenviron.<locals>.encodekey'报错解决

    关于selenium传参报错问题,用下面是报错信息: Traceback (most recent call last): File "D:/code/read_book/main.py&q ...

  2. 如何实现Windows平台RTMP播放器/RTSP播放器播放窗口添加OSD文字叠加

    好多开发者在做Windows平台特别是单屏多画面显示时,希望像监控摄像机一样,可以在播放画面添加OSD台标,以实现字符叠加效果,大多开发者可很轻松的实现以上效果,针对此,本文以大牛直播SDK (Git ...

  3. KingbaseFlySync 需要对外开放的端口

    Oracle到kes双轨灾备场景 源:Oracle rac 11g 目标端:kes v8r6c4b21 源.目标.管控服务器IP 需要开放端口 为什么源和目标需要互相开放数据库端口:因为在双轨运行的方 ...

  4. linux中cd后自动 ls的设置

    根据不同的shell设置不太一样.常见的有bash csh两种.可以用echo $SHELL来查询当前是哪一种. bash设置是在用户的home下打开.bashrc在里面加上如下: cd() { bu ...

  5. shell实现自动挂载优盘

    #!/bin/sh is_empty_dir(){ return `ls -A $1|wc -w` } is_usb_in(){ return `dmesg |tail -100000|grep -i ...

  6. 弱隔离级别 & 事务并发问题

    介绍弱隔离级别 为什么要有弱隔离级别 如果两个事务操作的是不同的数据, 即不存在数据依赖关系, 则它们可以安全地并行执行.但是当出现某个事务修改数据而另一个事务同时要读取该数据, 或者两个事务同时修改 ...

  7. 使用 EFKLK 搭建 Kubernetes 日志收集工具栈

    转载自:https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247491992&idx=1&sn=a770252759 ...

  8. MySQL 数据更新过程

    文章转载自:https://mp.weixin.qq.com/s?__biz=MzI1MDgwNzQ1MQ==&mid=2247486441&idx=1&sn=fcf93709 ...

  9. Nginx支持web界面执行bash|python等系统命令和脚本,可以传递参数

    文章转载自:https://me.jinchuang.org/archives/114.html ,有修改 步骤总结 1.安装好nginx,假设其html根路径为/usr/share/nginx/ht ...

  10. 第二章:视图层 - 1:URL路由基础

    路由的编写方式是Django2.0和1.11最大的区别所在.Django官方迫于压力和同行的影响,不得不将原来的正则匹配表达式,改为更加简单的path表达式,但依然通过re_path()方法保持对1. ...