当前位置: 代码网 > it编程>编程语言>Javascript > 使用 Go HTTP 框架 Hertz 进行 JWT 认证

使用 Go HTTP 框架 Hertz 进行 JWT 认证

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

前言

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

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

image-20221114215243547

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 的获取源,可以选择 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

更多 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"
}

参考文献

赞 (0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com