前言

上一篇文章简单介绍了一个高性能的 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. KingbaseES V8R3集群管理和维护案例之---failover切换wal日志变化分析

    ​ 案例说明: 本案例通过对KingbaseES V8R3集群failover切换过程进行观察,分析了主备库切换后wal日志的变化,对应用者了解KingbaseES V8R3(R6) failover ...

  2. SpringBoot_事务总结

    Springboot 事务 1. 打印SQL 日志的两种配置方式 [1]通过配置包的log等级来打印SQL日志,但这种不会打印出事务日志 logging.level.com.grady.mybatis ...

  3. Typora自动上传超级详细教程!!

    第一步检查环境变量 打开cmd 查看以下环境变量 需要软件: Typora PicGo gitee账号 配置node 配置git 第二步创建gitee仓库 设置仓库名直接创建,因为这里不能直接修改开源 ...

  4. MySQL 中的锁机制

    介绍锁机制 技术是为了解决问题而生的,锁被用来实现隔离性,保证并发事务的正确性. 两段锁 & 一次封锁 两段锁 数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁) ...

  5. 阿里云服务器如何使用phpmailer发送邮件

    原因是因为阿里云把25端口给禁用了,所以,普通方式发送不了,解决办法就是在阿里云安全组中把465端口打开,然后再才能发送邮件.亲测成功,贴出引用代码 function emailTo($user,$c ...

  6. fastapi教程进阶

    一个简单的栗子 from fastapi import FastAPI app = FastAPI() @app.get("/") async def root(): return ...

  7. Traefik 2.0 暴露 Redis(TCP) 服务

    文章转载自:https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247484452&idx=1&sn=0a17b907 ...

  8. Elastic:使用Postman来访问需要账号密码的Elastic Stack

  9. Tubian系统无法打开Android子系统的解决方法

    打开Konsole,Konsole在程序菜单(左下角Logo)-系统中 输入: sudo nano /var/lib/waydroid/waydroid.cfg 回车 按方向键,把光标移动到[prop ...

  10. python解释器下载及注意事项

    python解释器 python解释器下载 python解释器下载官网:https://www.python.org/downloads/windows/ 目前比较常用的python版本有python ...