前言

上一篇文章简单介绍了一个高性能的 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. Java常用类的使用

    Java常用类 1. Optional 在我们的开发中,NullPointerException可谓是随时随处可见,为了避免空指针异常,我们常常需要进行 一 些防御式的检查,所以在代码中常常可见if( ...

  2. SpringBoot多重属性文件配置方案笔记

    SpringBoot多重属性文件配置方案笔记 需要重写PropertyPlaceholderConfigurer 同时要忽略DataSourceAutoConfiguration @SpringBoo ...

  3. Odoo自建应用初步总结(一)

    学习了<Odoo快速入门与实践 Python开发ERP指南>(刘金亮 2019年5月第1版 机械工业出版社)第6章自建应用入门后进行一下总结. 因为本书作者使用Odoo11,而目前最新版本 ...

  4. IEEE浮点数向偶数舍

    CSAPP ​ 向偶数舍入初看上去好像是个相当随意的目标--有什么理由偏向取偶数呢?为什么不始终把位于两个可表示的值中间的值都向上舍入呢?使用这种方法的一个问题就是很容易假想到这样的情景:这种方法舍入 ...

  5. 使用NextCloud搭建私有网络云盘并支持Office文档在线预览编辑以及文件同步

    转载自:https://www.bilibili.com/read/cv16835328?spm_id_from=333.999.0.0 0x00 前言简述 描述:由于个人家里的NAS以及公司团队对私 ...

  6. kvm里的虚拟机硬盘和网卡使用virtio驱动

    1.首先从虚拟机的xml文件中找到已经使用virtio驱动的硬件,复制里面的address这行参数出来 <address type='pci' domain='0x0000' bus='0x00 ...

  7. 使用ConfigMap配置您的应用程序

    转载自:https://kuboard.cn/learning/k8s-intermediate/config/config-map.html ConfigMap 作为 Kubernetes API ...

  8. 在客户端电脑使用 kubectl 远程管理 Kubernetes

    日常工作中,可能需要在自己的笔记本电脑上执行 kubectl 命令以管理远程 Linux 服务器上的 Kubernetes 集群.通过调用 kubernetes API 来实现对 Kubernetes ...

  9. 堆Pwn:House Of Storm利用手法

    0x00:介绍 利用手法的背景: house of storm是一种结合了unsorted bin attack和Largebin attack的攻击技术,其基本原理和Largebin attack类 ...

  10. 分布式存储系统之Ceph基础

    Ceph基础概述 Ceph是一个对象式存储系统,所谓对象式存储是指它把每一个待管理的数据流(比如一个文件)切分成一到多个固定大小的对象数据,并以其为原子单元完成数据的存取:对象数据的底层存储服务由多个 ...