这是我参与「 第五届青训营 」伴学笔记创作活动的第 8 天 文章将介绍Hertz跨域配置Cors,权限校验jwt,异常处理recovery常用的中间件的使用。
主要内容
- CORS
- Recovery
- jwt
配置CORS
跨源资源共享(CORS)机制允许服务器标识除了它自己的其它 origin,使得浏览器可以访问加载这些资源; 该机制也用来检查服务器是否允许浏览器发送真实的请求,通过浏览器发送"预检"请求实现,在预检请求头部中有 HTTP 方法和真实请求会用到的头。
hertz 提供 cors 跨域中间件的实现 ,这里的实现参考了 gin 的 cors。
安装
go get github.com/hertz-contrib/cors
配置
在biz/router下创建公共中间件文件夹
// biz/router/middleware/cors.go
func CorsMw() app.HandlerFunc {
return cors.New(cors.Config{
// 允许跨源访问的 origin 列表
AllowOrigins: []string{"*"},
// 允许客户端跨源访问所使用的 HTTP 方法列表
AllowMethods: []string{"POST", "GET", "PUT", "DELETE", "OPTIONS"},
// 允许使用的头信息字段列表
AllowHeaders: []string{"Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma"},
// 允许暴露给客户端的响应头列表
ExposeHeaders: []string{"Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar"},
// 允许客户端请求携带用户凭证
AllowCredentials: true,
MaxAge: 12 * time.Hour,
})
}
在main.go中引用cors中间件
h.Use(middleware.CorsMw())
异常处理
Recovery中间件是Hertz框架预置的中间件,使用 server.Default() 可以默认注册该中间件,为Hertz框架提供panic恢复的功能。
如果你不使用server.Default(),你也可以通过以下方式注册Recovery中间件:
h := server.New()
h.Use(recovery.Recovery())
Recovery中间件会恢复Hertz框架运行中的任何panic,在panic发生之后,Recover中间件会默认打印出panic的时间、内容和堆栈信息,同时通过*app.RequestContext将返回响应的状态码设置成500。
封装异常结果类
// base/result/result.go
type GlobalError struct {
Id string `json:"id"`
Code int `json:"code"`
Detail string `json:"detail"`
Status string `json:"status"`
}
type IError struct {
Code int `json:"code"`
Detail string `json:"detail"`
}
func NewIError(code int, detail string) error {
jsonBytes, _ := json.Marshal(map[string]interface{}{
"Code": code,
"Detail": detail,
})
return errors.New(string(jsonBytes))
}
func NewError(code int, detail string) GlobalError {
err := GlobalError{
Code: code,
Detail: detail,
}
return err
}
Recovery配置
Recovery中间件提供了默认的panic处理函数defaultRecoveryHandler()。同时你也可以通过WithRecoveryHandler()函数来自定义出现panic后的处理函数
// biz/handler/myHandler/recovery.go
func Handler(c context.Context, ctx *app.RequestContext, err interface{}, stack []byte) {
// 日志打印包 代码请见Github
logging.Info(c, "[Recovery] myRecovery =", err, string(stack), string(ctx.Request.Header.UserAgent()))
errStr := make(map[string]interface{}, 3)
switch err.(type) {
case result.GlobalError:
globalError := err.(result.GlobalError)
errStr = map[string]interface{}{
"code": globalError.Code,
"detail": globalError.Detail,
"data": nil,
}
case error:
var ie result.IError
iError := err.(error)
_ = json.Unmarshal([]byte(iError.Error()), &ie)
errStr = map[string]interface{}{
"code": ie.Code,
"detail": ie.Detail,
"data": nil,
}
}
ctx.JSON(500, errStr)
ctx.AbortWithStatus(consts.StatusInternalServerError)
}
在main.go中引入
h.Use(recovery.Recovery(recovery.WithRecoveryHandler(myHandler.Handler)))
用户认证
JSON Web Token(JWT)是一个轻量级的认证规范,这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。其本质是一个 token ,是一种紧凑的 URL 安全方法,用于在网络通信的双方之间传递。 Hertz 也提供了 jwt 的实现 ,参考了 gin 的实现 。
安装
go get github.com/hertz-contrib/jwt
配置
// biz/router/middleware/jwt.go
// 定义用户登陆信息结构体
type Claim struct {
ID int
Username string
}
func JwtMwInit() {
// the jwt middleware
authMiddleware, err := jwt.New(&jwt.HertzJWTMiddleware{
// 置所属领域名称
Realm: "hertz jwt",
// 用于设置签名密钥
Key: []byte("hertz-mylist&&sinrewxljntm"),
// 设置 token 过期时间
Timeout: time.Hour * 8,
// 设置最大 token 刷新时间
MaxRefresh: time.Hour * 4,
// 设置 token 的获取源
TokenLookup: "header: Authorization",
// 设置从 header 中获取 token 时的前缀
TokenHeadName: "Bearer",
// 用于设置检索身份的键
IdentityKey: IdentityKey,
// 登陆成功后为向 token 中添加自定义负载信息
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(Claim); ok {
return jwt.MapClaims{
IdentityKey: v,
}
}
return jwt.MapClaims{}
},
// 从 token 提取用户信息
IdentityHandler: func(ctx context.Context, c *app.RequestContext) interface{} {
claims := jwt.ExtractClaims(ctx, c)
claim := claims[IdentityKey].(map[string]interface{})
return &Claim{
ID: int(claim["ID"].(float64)),
Username: claim["Username"].(string),
}
},
// 认证
Authenticator: func(ctx context.Context, c *app.RequestContext) (interface{}, error) {
var loginUser user.User
if err := c.BindAndValidate(&loginUser); err != nil {
return "", result.NewIError(10000, "数据绑定错误")
}
password := loginUser.Password
if err := gorm.DB.Where("username = ?", loginUser.Username).First(&loginUser).Error; err != nil {
return nil, result.NewIError(10001, "用户名不存在")
}
if check := loginUser.CheckPassword(password); check {
return Claim{
ID: loginUser.ID,
Username: loginUser.Username,
}, nil
} else {
return nil, result.NewIError(10002, "密码错误")
}
},
// 鉴权
Authorizator: func(data interface{}, ctx context.Context, c *app.RequestContext) bool {
// 单一角色 不设权限校验
return true
},
// 登录响应
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,
"detail": "登陆成功",
})
},
// 设置 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: func(ctx context.Context, c *app.RequestContext, code int, message string) {
c.JSON(http.StatusOK, utils.H{
"code": 10011,
"detail": "无权限或用户认证已过期",
"data": nil,
})
},
})
if err != nil {
log.Fatal("JWT Error:" + err.Error())
}
HzJwtMw = authMiddleware
}
在main.go中初始化jwt配置
middleware.JwtMwInit()
在需要鉴权的路由上引入
func _taskMw() []app.HandlerFunc {
// your code...
return []app.HandlerFunc{middleware.HzJwtMw.MiddlewareFunc()}
}
认证流程
- 配合
HertzJWTMiddleware.LoginHandler使用,用户登陆调用接口进入Authenticator校验用户名密码是否正确
Authenticator: func(ctx context.Context, c *app.RequestContext) (interface{}, error) {
// todo 校验用户名密码
// todo 用户名密码正确返回
return Claim, nil
// todo 用户名密码错误返回
return nil, err
},
- 登陆成功进入
PayloadFunc,向token中添加自定义负载信息
PayloadFunc: func(data interface{}) jwt.MapClaims {
// todo 返回自定义负载信息内容
return jwt.MapClaims{}
},
- 生成
jwt后进入LoginResponse返回登陆信息
LoginResponse: func(ctx context.Context, c *app.RequestContext, code int, token string, expire time.Time) {
// todo 返回信息
},
- 登陆失败进入
HTTPStatusMessageFunc设置失败返回信息
Unauthorized: func(ctx context.Context, c *app.RequestContext, code int, message string) {
// todo 返回信息
},
鉴权流程
- 校验jwt通过后进入
IdentityHandler从token中提取用户负载信息
IdentityHandler: func(ctx context.Context, c *app.RequestContext) interface{} {
// todo 返回用户负载信息
return Claim
},
Authorizator校验权限,判断是否有访问路由的权限
Authorizator: func(data interface{}, ctx context.Context, c *app.RequestContext) bool {
// todo 设置鉴权方式
// todo 鉴权成功返回
return true
// todo 鉴权失败返回
return false
},
Unauthorized鉴权失败,返回信息
Unauthorized: func(ctx context.Context, c *app.RequestContext, code int, message string) {
// todo 返回信息
},
源码
Hanabi-wxl/hertz-mw-example (github.com)