

使用 Go HTTP 框架 Hertz 进行 JWT 认证 - 白泽来了
source link: https://www.cnblogs.com/YLTFY1998/p/16898513.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

上一篇文章简单介绍了一个高性能的 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 完成持久化,因此在登陆之前,需要对用户进行注册,注册流程为:
- 获取用户名密码和邮箱
- 判断用户是否存在
用户登陆(认证)
服务器需要在用户第一次登陆的时候,验证用户账号和密码,并签发 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 的获取源,可以选择
header
、query
、cookie
、param
,默认为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 操作 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": "[email protected]",
"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"
}
Recommend
-
8
面试官:请分析一条SQL的执行 关注公众号【程序员白泽】,带你走进一个不一样的程序员/学生党 最近一直在写《手撕MySQL系...
-
4
字节跳动开源 Go HTTP 框架 Hertz 设计实践-51CTO.COM 字节跳动开源 Go HTTP 框架 Hertz 设计实践 原创
-
5
字节跳动开源 Go HTTP 框架 Hertz 设计实践By CloudWeGo | Tuesday, June 21, 2022Hertz 是字节跳动服务框架团队研发的超大规模的企业级微服务 HTTP 框架,具...
-
9
写作本文的背景是由于字节的暑期青训营中,某个项目要求编写一个简易的流处理引擎(flink),开发语言不限,推荐Java,本着好奇心的驱使,我打算使用Go语言进行部分尝试。 既然是流处理引擎,那么首先需...
-
5
本篇文章将模拟一个KV数据读写服务,从提供单一节点读写服务,到结合分布式一致性协议(Raft)后,逐步扩展为一个分布式的,满足一致性读写需求的读写服务的过程。 其中将配合引入Raft协议的种种概念:选主、一致...
-
5
玩转 Go 生态|Hertz WebSocket 扩展简析 WebSocket 是一种可以在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。...
-
4
...
-
8
Hertz 支持 QUIC & HTTP/3 本文介绍了 Hertz 为支持 QUIC & HTTP/3 在网络传输层和协议层提供的接口设计方案。 By CloudWeGo | Wednesday, August 02, 2023
-
6
🌟 白泽时隔8年终于记起了b站的密码,这篇文章的视频讲解版已经上传,出镜怪不好意思的,后面写技术文章也会同步用视频的方式讲解,期待您的关注...
-
3
大家好,这里是白泽。《Go语言的100个错误以及如何避免》是最近朋友推荐我阅读的书籍,我初步浏览之后,大为惊喜。就像这书中第一章的标题说到的:“Go: Simple to learn but hard to master”,整本书通过分析100个错误使用 Go 语...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK