【CloudWeGo】字节跳动 Golang 微服务框架 Hertz 集成 Jwt

hello,大家好,我是千羽。
前几天聊了持久层框架:gorm、gen等等,今天探讨如何在 Hertz 中集成 JWT。
一、JWT:安全令牌的核心
JWT(JSON Web Token) 对于程序员应该是最常见不过了,主要的应用场景也身份认证,比如你在抖音点赞收藏等等都需要登录验证信息,同时,在微服务架构日益流行的今天,如何确保服务之间通信的安全是至关重要的问题。
JWT 作为一种开放标准(RFC 7519),在身份验证和授权方面有着独特的优势。它由头部(Header)、载荷(Payload)和签名(Signature)三部分组成。
头部包含了令牌的类型(JWT)和所使用的签名算法信息,如 HMAC SHA256 等。载荷则承载了用户相关的信息,像用户 ID、角色、权限等,这些信息经过编码后被封装在令牌内。而签名是通过对头部和载荷使用特定密钥和算法生成的,用于保证令牌的完整性和真实性。
在微服务通信中,JWT 可以作为一种轻量级的、自包含的认证和授权凭证,服务端可以轻松地验证令牌的有效性,从而决定是否允许请求的访问。
二、hertz_jwt 项目目录结构
以下是 Hertz 项目的基本目录结构,展示了如何集成 JWT:
├── Makefile # 定义项目的自动化任务脚本。
├── biz
│ ├── dal
│ │ ├── init.go
│ │ └── mysql
│ │ ├── init.go # 连接信息
│ │ └── user.go # 包含对用户数据在 MySQL 中的操作。
│ ├── handler
│ │ ├── ping.go
│ │ └── register.go # 处理用户注册handler请求。
│ ├── model
│ │ ├── sql
│ │ │ └── user.sql # 定义用户数据结构体和相关方法。
│ │ └── user.go
│ ├── mw
│ │ └── jwt.go # JWT 相关的中间件代码。
│ ├── router
│ │ └── register.go
│ └── utils
│ └── md5.go
├── docker-compose.yml # 定义和运行多个 Docker 容器的配置文件。
├── go.mod
├── go.sum
├── main.go
├── readme.md
├── router.go # l
└── router_gen.go

三、如何运行
Step 1:使用 Docker 启动 MySQL容器
cd bizdemo/hertz_jwt && docker-compose up

docker运行mysql
Step 2:编译并运行项目
cd bizdemo/hertz_jwt && go run main.go
看到下面的日志👇,就说明启动成功啦
HERTZ: HTTP server listening on address=[::]:8888

四、API 接口调试
根据启动的日志,我们进行各个接口验证

  1. /register 用户注册接口
    请求url:http://localhost:8888/register
    请求参数:
    {
    “username”: “hertz_jwt”,
    “email”: “1122@qq.com”,
    “password”: “password”
    }
    响应结果:
    {
    “code”: 200,
    “message”: “success”
    }

数据库验证:

  1. /login 用户登录接口
    请求:http://localhost:8888/login
    请求参数:
    {
    “account”: “hertz_jwt”,
    “password”: “password”
    }
    响应结果
    {
    “code”: 200,
    “expire”: “2024-11-10T21:57:05+08:00”,
    “message”: “success”,
    “token”: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzEyNDcwMjUsImlkZW50aXR5IjoiaGVydHpfand0Iiwib3JpZ19pYXQiOjE3MzEyNDM0MjV9.lOnyoa-VSz9yTbUdjxF9h2I-4iJNbHh2vfqSqRE7mYI”
    }
  2. /ping 接口
    请求:http://localhost:8888/ping
    需要在auth里头,选择Bear Token 把登录成功返回的token的一大串字符串复制进去就好
    响应结果:
    {
    “message”: “username:hertz_jwt”
    }
    五、代码解析
    5.1 /register 用户注册接口代码解析
    Hertz 中集成 JWT 的步骤
    (一)引入相关库
    首先,在你的 Hertz 项目中,需要引入处理 JWT 的 Golang 库。有多种选择,比如 “github.com/golang-jwt/jwt/v4″,这个库提供了丰富的功能来生成、解析和验证 JWT。
    (二)生成 JWT 令牌
    在用户认证成功的逻辑中,我们可以使用密钥和相关用户信息来生成 JWT。例如:
    (三)在 Hertz 中间件中验证 JWT
    创建一个 Hertz 中间件来拦截请求,并验证请求中携带的 JWT。
    (四)注册中间件到 Hertz 路由
    在 Hertz 的路由设置中,将 JwtAuthMiddleware 注册到需要认证的路由上。
    5.1.1 路由 router.go
    // 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)
    }

5.1.2 Register handler
// Register user register handler
func Register(ctx context.Context, c app.RequestContext) { // 定义一个结构体用于接收用户注册时传入的参数 var registerStruct struct { Username string form:"username" json:"username" query:"username" vd:"(len($) > 0 && len($) < 128); msg:'Illegal format'" Email string form:"email" json:"email" query:"email" vd:"(len($) > 0 && len($) < 128) && email($); msg:'Illegal format'" Password string form:"password" json:"password" query:"password" vd:"(len($) > 0 && len($) < 128); msg:'Illegal format'" } // 绑定请求中的参数到结构体并进行验证 if err := c.BindAndValidate(®isterStruct); err != nil { c.JSON(http.StatusOK, utils.H{ “message”: err.Error(), “code”: http.StatusBadRequest, }) return } // 根据用户名和邮箱查询用户是否已存在 users, err := mysql.FindUserByNameOrEmail(registerStruct.Username, registerStruct.Email) if err != nil { c.JSON(http.StatusOK, utils.H{ “message”: err.Error(), “code”: http.StatusBadRequest, }) return } // 如果查询到的用户数量不为 0,说明用户已存在 if len(users) != 0 { c.JSON(http.StatusOK, utils.H{ “message”: “user already exists”, “code”: http.StatusBadRequest, }) return } // 创建新用户,对密码进行 MD5 加密后存储 if err = mysql.CreateUsers([]model.User{
{
UserName: registerStruct.Username,
Email: registerStruct.Email,
Password: utils2.MD5(registerStruct.Password),
},
}); err != nil {
// 如果创建用户过程中出现错误,返回错误信息和状态码
c.JSON(http.StatusOK, utils.H{
“message”: err.Error(),
“code”: http.StatusBadRequest,
})
return
}
// 注册成功,返回成功信息和状态码
c.JSON(http.StatusOK, utils.H{
“message”: “success”,
“code”: http.StatusOK,
})
}
mysql操作层实现
func CreateUsers(users []*model.User) error {
return DB.Create(users).Error
}

func FindUserByNameOrEmail(userName, email string) ([]model.User, error) { res := make([]model.User, 0)
if err := DB.Where(DB.Or(“user_name = ?”, userName).
Or(“email = ?”, email)).
Find(&res).Error; err != nil {
return nil, err
}
return res, nil
}

func CheckUser(account, password string) ([]model.User, error) { res := make([]model.User, 0)
if err := DB.Where(DB.Or(“user_name = ?”, account).
Or(“email = ?”, account)).Where(“password = ?”, password).
Find(&res).Error; err != nil {
return nil, err
}
return res, nil
}
5.2 登录实现
通过JwtMiddleware登录相对简单一些,直接是一行代码搞定。
r.POST(“/login”, mw.JwtMiddleware.LoginHandler)
通过分析auth_jwt.go源码可以发现首先检查是否设置了认证函数,如果没有则返回内部服务器错误。
然后调用认证函数进行用户认证,如果认证失败则返回未授权错误。接着创建 JWT 令牌,设置令牌的载荷和过期时间,并对令牌进行签名。
如果设置了发送 Cookie,则将令牌字符串设置到 Cookie 中。
最后调用登录响应函数,返回登录成功的状态码、令牌字符串和过期时间。
5.3 ping接口
最后的ping接口,和前面的例子有区别,这次是加了token鉴权处理的,需要拿到token进行才能请求成功。
// 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),
})
}

分析一下c.Get()获取key方法实现
// Get returns the value for the given key, ie: (value, true).
// If the value does not exist it returns (nil, false)
func (ctx *RequestContext) Get(key string) (value interface{}, exists bool) {
ctx.mu.RLock()
value, exists = ctx.Keys[key]
ctx.mu.RUnlock()
return
}
对请求上下文的互斥锁(mu)加读锁,以确保在读取Keys字典时的线程安全。
然后,在Keys字典中查找给定的键,并将找到的值和一个表示键是否存在的布尔值分别赋值给value和exists变量。最后,解读锁并返回找到的值和是否存在的标志。
这种情况下安全地获取存储在其中的特定键的值,适用于在处理请求过程中需要获取上下文特定数据的场景。
参考文献:
https://www.cloudwego.io/zh/docs/hertz

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/423631.html

联系我们
联系我们
分享本页
返回顶部