Hertz框架的JWT使用

Hertz框架的JWT使用

​ 基于Hertz官方demo。https://github.com/cloudwego/hertz-examples

文件夹结构

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

1. 用户注册

1.1 首先构建User结构体

biz/model/user.go

package model

import "gorm.io/gorm"

type User struct {
	gorm.Model
	UserName string `json:"user_name" column:"user_name"` // column 为数据库中的列名
	Email    string `json:"email" column:"email"`
	Password string `json:"password" column:"password"`
}
// 表名
func (u *User) TableName() string {
	return "users"
}
1.2 连接数据库

/biz/dal/mysql/init.go

package mysql

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var DB *gorm.DB

func Init() {
    // 这里的username,password,Dbname要改成自己的,用的云服务的话host也要修改
	username := "root"  //账号
	password := "1234"  //密码
	host := "127.0.0.1" //数据库地址,可以是Ip或者域名
	port := 3306        //数据库端口
	Dbname := "gorm"    //数据库名
	timeout := "10s"    //连接超时,10秒

	var err error
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)
	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
		SkipDefaultTransaction: true,
		PrepareStmt:            true,
		Logger:                 logger.Default.LogMode(logger.Info),
	})
	if err != nil {
		panic(err)
	}
}

/biz/dal/init.go 注意,这里和上面的路径不一样,是上一级路径。 后续使用可以直接调用该init方法初始化。

package dal

import "hertz_jwt/biz/dal/mysql"

func Init() {
	mysql.Init()
}
1.3 注册用的相关方法以及md5加密

/biz/dal/mysql/user.go

package mysql

import (
	"hertz_jwt/biz/model"
)

func CreateUsers(users []*model.User) error {
	return DB.Create(users).Error
}

func FindUserByNameOrEmail(userName string, 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
	}
	//.Find(&res):执行查询操作,并将结果存储到res指向的切片中。
	//.Error:检查是否有错误发生。
	return res, nil
}

biz/utils/md5.go

package utils

import (
	"crypto/md5"
	"encoding/hex"
)

func MD5(str string) string {
	h := md5.New()
	// 将字符串转为字节数组后写入md5对象
	h.Write([]byte(str))
	//h.Sum(nil):计算并返回当前哈希对象的哈希值。由于MD5的哈希值是一个固定长度的字节切片(16字节),
	//Sum方法接受一个字节切片作为参数,并在其上追加哈希值。
	//在这里,我们传入nil作为参数,因为我们不需要在哈希值前面添加额外的数据。
	// hex.EncodeToString() ,将哈希值切片编码成字符串
	return hex.EncodeToString(h.Sum(nil))
}
1.4 注册

biz/handler/register.go

package handler

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"hertz_jwt/biz/dal/mysql"
	"hertz_jwt/biz/model"
	utils2 "hertz_jwt/biz/utils"
	"net/http"
)

// Register 用户注册
func Register(ctx context.Context, c *app.RequestContext) {
	// 注册用户结构体,包含数据验证相关
	var registerStruct struct {
		// form:指明表单中字段名
		UserName string `form:"username" json:"username" query:"username" vd:"(len($)>0&&len($)<128); message:'Illegal format'"`
		Email    string `form:"email" json:"email" query:"email" vd:"(len($)>0&&len($)<128)&&email($); message:'Illegal format'"`
		Password string `form:"password" json:"password" query:"password" vd:"(len($)>0&&len($)<128); message:'Illegal format'"`
	}
	// 绑定参数
	if err := c.BindAndValidate(&registerStruct); 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
	}
	// 用户名或邮箱注册过了
	if len(users) != 0 {
		c.JSON(http.StatusOK, utils.H{
			"message": "user already exists",
			"code":    http.StatusBadRequest,
		})
		return
	}
	// 没有注册过,创建用户
	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,
	})
}

2. 用户登录(认证)

biz/mw/jwt.go

package mw

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/hertz-contrib/jwt"
	"hertz_jwt/biz/dal/mysql"
	"hertz_jwt/biz/model"
	utils2 "hertz_jwt/biz/utils"
	"net/http"
	"time"
)

var (
	JwtMiddleware *jwt.HertzJWTMiddleware
	IdentityKey   = "identity"
)

func InitJwt() {
	var err error
	JwtMiddleware, err = jwt.New(&jwt.HertzJWTMiddleware{
		Realm: "test zone",
		// Key:指定了用于加密 jwt token 的密钥为 "secret key"。
		Key: []byte("secret key"),
		// Timeout:指定了 token 有效期为一个小时。
		Timeout: time.Hour,
		// MaxRefresh:用于设置最大 token 刷新时间,允许客户端在 TokenTime + MaxRefresh 内刷新 token 的有效时间,追加一个 Timeout 的时长。
		MaxRefresh: time.Hour,
		// Authenticator:用于设置登录时认证用户信息的函数,demo 当中定义了一个 loginStruct 结构接收用户登陆信息,并进行认证有效性。这个函数的返回值 users[0] 将为后续生成 jwt token 提供 payload 数据源。
		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
			}
			return users[0], nil
		},
		// PayloadFunc:它的入参就是 Authenticator 的返回值,此时负责解析 users[0],并将用户名注入 token 的 payload 部分。
		PayloadFunc: func(data interface{}) jwt.MapClaims {
			// data.(*model.User),类型断言来检查传入的data是否是*model.User类型的指针。如果类型断言成功,v将包含原始的model.User对象,ok将为true
			if v, ok := data.(*model.User); ok {
				return jwt.MapClaims{
					IdentityKey: v.UserName,
				}
			}
			return jwt.MapClaims{}
		},
		// token 返回
		//LoginResponse:在登陆成功之后,jwt token 信息会随响应返回,你可以自定义这部分的具体内容,但注意不要改动函数签名,因为它与 LoginHandler 是强绑定的。
		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",
			})
		},
		// token 校验
		// 用于设置 token 的获取源,可以选择 header、query、cookie、param,默认为 header:Authorization,同时存在是以左侧一个读取到的优先。
		// 当前 demo 将以 header 为数据源,因此在访问 /ping 接口时,需要你将 token 信息存放在 HTTP Header 当中。
		TokenLookup: "header:Authorization,query:token,cookie:jwt",
		// TokenHeadName:用于设置从 header 中获取 token 时的前缀,默认为 "Bearer"。
		TokenHeadName: "Bearer",
		// HTTPStatusMessageFunc:用于设置 jwt 校验流程发生错误时响应所包含的错误信息,你可以自行包装这些内容。
		HTTPStatusMessageFunc: func(e error, ctx context.Context, c *app.RequestContext) string {
			hlog.CtxErrorf(ctx, "jwt biz err = %+v", e.Error())
			return e.Error()
		},
		// Unauthorized:用于设置 jwt 验证流程失败的响应函数,当前 demo 返回了错误码和错误信息。
		Unauthorized: func(ctx context.Context, c *app.RequestContext, code int, message string) {
			c.JSON(http.StatusOK, utils.H{
				"code":    code,
				"message": message,
			})
		},
		// 用户信息提取
		// IdentityKey:用于设置检索身份的键,默认为 "identity"。
		IdentityKey: IdentityKey,
		// IdentityHandler:用于设置获取身份信息的函数,在 demo 中,此处提取 token 的负载,并配合 IdentityKey 将用户名存入上下文信息。
		IdentityHandler: func(ctx context.Context, c *app.RequestContext) interface{} {
			claims := jwt.ExtractClaims(ctx, c)
			return &model.User{
				UserName: claims[IdentityKey].(string),
			}
		},
	})
	if err != nil {
		panic(err)
	}
}

biz/handler/ping.go

package handler

import (
	"context"
	"fmt"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"hertz_jwt/biz/model"
	"hertz_jwt/biz/mw"
)

// 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),
	})
}

3. 访问验证

​ 1. 需要创建数据库

CREATE TABLE `users`
(
    `id`         bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'PK',
    `user_name`  varchar(128) NOT NULL DEFAULT '' COMMENT 'User name',
    `email`      varchar(128) NOT NULL DEFAULT '' COMMENT 'User email',
    `password`   varchar(128) NOT NULL DEFAULT '' COMMENT 'User password',
    `created_at` timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'User information create time',
    `updated_at` timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'User information update time',
    `deleted_at` timestamp NULL DEFAULT NULL COMMENT 'User information delete time',
    PRIMARY KEY (`id`),
    UNIQUE KEY `idx_email` (`email`) COMMENT 'User email index',
    UNIQUE KEY `idx_user_name` (`user_name`) COMMENT 'User name index'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='User information table'
  1. biz/router/register.go 以下四个文件都可直接从Hertz的demo项目中拷贝。

    https://github.com/cloudwego/hertz-examples

// Code generated by hertz generator. DO NOT EDIT.

package router

import (
	"github.com/cloudwego/hertz/pkg/app/server"
)

// GeneratedRegister registers routers generated by IDL.
func GeneratedRegister(r *server.Hertz) {
	//INSERT_POINT: DO NOT DELETE THIS LINE!
}
  1. router.go
// Code generated by hertz generator.

package main

import (
	"github.com/cloudwego/hertz/pkg/app/server"
	"hertz_jwt/biz/handler"
	"hertz_jwt/biz/mw"
)

// 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)
}

​ 4.router_gen.go

// Code generated by hertz generator. DO NOT EDIT.

package main

import (
	router "hertz_jwt/biz/router"
	"github.com/cloudwego/hertz/pkg/app/server"
)

// register registers all routers.
func register(r *server.Hertz) {

	router.GeneratedRegister(r)

	customizedRegister(r)
}

​ 5.main.go

// Code generated by hertz generator.

package main

import (
	"github.com/cloudwego/hertz/pkg/app/server"
	"hertz_jwt/biz/dal"
	"hertz_jwt/biz/mw"
)

func main() {
	dal.Init()
	mw.InitJwt()
	h := server.Default()

	register(h)
	h.Spin()
}

上述代码也可通过hz命令行生成。

参考:https://juejin.cn/post/7166600531434012679。代码生成部分。

这部分go install 安装完成后,hz -v 仍然不存在hz这个命令,不太知道要怎么搞了,有会的可以在评论区告知一下。

  1. 访问

    以下操作在postman中执行,因为没有前端页面。

​ 注册:

POST:
http://127.0.0.1:8888/register

body:
{
    "Username": "admin",
    "Email": "admin@test.com",
    "Password": "admin"
}// 选择json

return:
{
    "code": 200,
    "message": "success"
}

​ 登录:

POST:
http://127.0.0.1:8888/login

body:
{
    "Account": "admin",
    "Password": "admin"
}// 选择json

return:
{
    "code": 200,
    "expire": "2024-04-17T15:44:38+08:00",
    "message": "success",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTMzMzk4NzgsImlkZW50aXR5IjoiYWRtaW4iLCJvcmlnX2lhdCI6MTcxMzMzNjI3OH0.uJYKwHAfbUOJyGHoRQdUqp6jYHDQZvkEG4Fve-lf9qg"
}

​ 验证:

GET:
http://127.0.0.1:8888/auth/ping

header中添加:
Authorization:Bearer {{token}} //将token添加为全局变量,值为上面的token

return:
{
    "message": "username:admin"
}

本文是对https://juejin.cn/post/7166600531434012679的学习和总结,有需要的可以参考这篇。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值