自学内容网 自学内容网

go-zero(六) JWT鉴权

go-zero JWT鉴权

还记得我们之前登录功能,返回的信息是token吗? 这个token其实就是JSON Web Token简称JWT,它是一种开放标准(RFC 7519),用于在网络应用环境间安全地传递声明信息。

它是一种基于 JSON 的令牌,由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。签名是将头部和载荷组合后使用密钥生成的哈希值,用于验证令牌的真实性。

一、配置Auth

Auth是 jwt 密钥,过期等信息配置的 golang 结构体名称, 所以我们要在user-api.yaml中配置Auth

Auth:
  AccessSecret: "sss12345678" # AccessSecret的值要是8位以上的字符串
  AccessExpire: 10000 #AccessExpire是过期时间,单位是秒

注意:Auth的名字要和jwt: Auth 这个传入的一样。

接在我们在config.go文件里面定义用来解析配置文件的结构体

type Config struct {
rest.RestConf

MysqlDb struct {
DbSource string `json:"dbSource"`
}
//增加Auth 结构体
Auth struct {   
AccessSecret string
AccessExpire int64
}
}

到这边我们初步配置就完成了

二、生成JWT

为了简化操作,我们直接在loginlogic.go 代码中添加生成token的方法:

/*
secretKey string: 用于加密 JWT 的密钥,通常是一个足够复杂的随机字符串,确保 JWT 不易被伪造。
iat int64: 表示 JWT 的签发时间(Issued At),为一个时间戳,通常是当前时间的 UNIX 时间戳(以秒为单位),可以通过 time.Now().Unix() 获得。
seconds int64: 表示 JWT 的有效时长,单位为秒。这是通过 iat 参数计算出 JWT 的过期时间(exp)。
username string: 自定义有效载荷,通常是是使用用户唯一性的数据作为载体,这里我们为了方便演示使用了username。
*/

func getJwtToken(secretKey string, iat, seconds int64,username string) (string, error) {
 // 创建一个 MapClaims 类型的声明
    claims := make(jwt.MapClaims)
    // 计算过期时间
    claims["exp"] = iat + seconds  // 设置 JWT 的过期时间(exp),通常需要一个 UNIX 时间戳
    claims["iat"] = iat            // 设置签发时间(iat)
    claims["username"] = username    // 自定义的负载(payload),可以设置为任何信息,例如用户名、用户ID等
    // 创建新的 JWT
    token := jwt.New(jwt.SigningMethodHS256) // 使用 HMAC SHA256 签名方法创建新的 JWT
    // 将声明分配给 JWT
    token.Claims = claims
    // 使用 secretKey 签名JWT,并返回生成的字符串和错误(如果有)
    return token.SignedString([]byte(secretKey))
}

JWT有三种加密方式分别是 :SigningMethodHS256SigningMethodHS384SigningMethodHS512 ,这里我们用的是hs256

然后修改Login中的代码,添加token生成功能。

go-zero中jwt的传递是在HTTP请求添加名为Authorization的header,形式如下 Authorization: Bearer <token> ,注意 Bearer <token> 之间有空格

func (l *LoginLogic) Login(req *types.LoginRequest) (resp *types.LoginResponse, err error) {
// todo: add your logic here and delete this line

//因为我们目前还没涉及到jwt鉴权,所以先把token当面message使用
userModel := l.svcCtx.UserModel
user, _ := userModel.FindOneByUsername(l.ctx, req.Username)

//从配置文件中获取secret 、secret 
secret := l.svcCtx.Config.Auth.AccessSecret
expire := l.svcCtx.Config.Auth.AccessExpire
//生成jwt token
token, err := getJwtToken(secret, time.Now().Unix(), expire, req.Username)
if err != nil {
return nil, errors.New(4, "token生成失败")
}
//查询username判断是否有数据
if user != nil {
//如果有数据,密码是否和数据库匹配
if req.Password == user.Password {

return &types.LoginResponse{
Token:"Bearer "+ token, //开头添加Bearer 
}, nil
} else {
return nil, errors.New(5, "密码错误")
}
} else {
return nil, errors.New(6, "用户未注册")
}
}

注意:从后面我们响应信息默认使用 xhttp.JsonBaseResponseCtx() 即zeromicro的x库

运行项目,测试结果如下:
在这里插入图片描述

JWT验证go-zero自动帮我们做了,所以我们不需要单独的去写一个验证方法。

三、使用jwt

注册和登录的功能已经实现,现在我们来实现用户的查询和更新功能,这两个功能在正常的业务逻辑中都是需要通过登录实现的,也就是说需要使用jwt来验证和传递信息。

现在我们来编辑新的api文件,并演示如何启用jwt, 在api中我们通过可选参数【jwt:Auth 】来控制是否启用 JWT。具体实现如下:

syntax = "v1"

/*
省略注册和登录
*/

//因为我们想要通过jwt来传递数据,所以我们不需求请求信息
type (
GetInfoResponse {
Username string `json:"username" `
Password string `json:"password" `
}
)


type (
//更新数据我们就简单修改下密码
UpdataRequest {
Password string `json:"password" `
}
UpdataResponse {
Message string `json:"message"`
}
)

@server (
group:  user
prefix: /v1
jwt:    Auth //开启jwt
)
service user-api {
@handler GetInfoHandler
// 不需要请求信息
post /getinfo returns (GetInfoResponse)

@handler UpdataHandler
post /updata (UpdataRequest) returns (UpdataResponse)
}

接下来我们使用goctl生成新的代码

goctl api go --api user.api --dir ./

我们来看下go-zero是怎么帮我们实现jwt认证的,我们先看下routes.go 文件。

func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {

/*
省略注册和登录的代码
*/
server.AddRoutes(
[]rest.Route{
{
Method:  http.MethodPost,
Path:    "/getinfo",
Handler: getinfo.GetInfoHandler(serverCtx),
},
},
//开启jwt
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/v1"),
)

server.AddRoutes(
[]rest.Route{
{
Method:  http.MethodPost,
Path:    "/updata",
Handler: updata.UpdataHandler(serverCtx),
},
},
//开启jwt
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/v1"),
)
}

goctl 帮我在/getinfo/updata两个路由使用rest.WithJwt开启了jwt验证。

四、获取载体信息

1. 实现信息查询

我们首先去实现查询和更新这两个业务逻辑,到logic目录下打开getinfologic.go 修改代码如下:

func (l *GetInfoLogic) GetInfo() (resp *types.GetInfoResponse, err error) {
// todo: add your logic here and delete this line
//使用l.ctx.Value() 来获取jwt的内容
username := l.ctx.Value("username").(string)

usermodel := l.svcCtx.UserModel

user, err := usermodel.FindOneByUsername(l.ctx, username)
if err != nil && err != model.ErrNotFound {
return nil, errors.New(0, "数据库连接失败")
}

if user == nil {
return nil, errors.New(11, "查询失败,没有该用户信息")
}

return &types.GetInfoResponse{
Username: user.Username,
Password: user.Password,
}, nil
return
}

在go-zero中使用l.ctx.Value()来获取数据, 这里我们使用的username,其实就是我们获取token的时候传入的自定义payload, 总而言之就是你传什么字段就获取什么字段。因为Value()any类型,所以需要对它进行断言。

我们先不传递Authorization的值,运行项目测试一下,可以发现直接报错了
在这里插入图片描述

我们可以看下运行日志,提示我们认证失败,没有token

在这里插入图片描述

现在添加Authorization的值再测试下,可以看到成功执行:
在这里插入图片描述

2. 实现信息修改

func (l *UpdataLogic) Updata(req *types.UpdataRequest) (resp *types.UpdataResponse, err error) {
// todo: add your logic here and delete this line

username := l.ctx.Value("username").(string)

usermodel := l.svcCtx.UserModel
user, err := usermodel.FindOneByUsername(l.ctx, username)
if err != nil && err != model.ErrNotFound {
return nil, errors.New(0, "数据库连接失败")
}

if user == nil {
return nil, errors.New(11, "查询失败,没有该用户信息")
}
newUser := model.Users{
Id:        user.Id,
Username:  user.Username,
Password:  req.Password, //使用请求响应的密码
CreatedAt: user.CreatedAt,
}
err = usermodel.Update(l.ctx, &newUser)
if err != nil {
return nil, errors.New(12, "数据更新失败")
}
return &types.UpdataResponse{
Message: "数据更新成功",
}, nil

}

运行项目测试:

在这里插入图片描述

五、JWT 认证失败自定义处理返回

刚刚我们测试了一下JWT认证失败的情况,它直接给我们返回来401代码,这显然不是我们想要的,现在我们来看下JWT 认证失败自定义处理返回。

我们需要在main中定义一个callback,并注册到服务中 , rest.WithUnauthorizedCallback 会全局捕捉jwt认证失败的请求

    server := rest.MustNewServer(c.RestConf, rest.WithUnauthorizedCallback(func(w http.ResponseWriter, r *http.Request, err error) {

接下来我们来具体实现它:

//定义一个返回信息
func unauthorizedHandler(w http.ResponseWriter, r *http.Request, err error) {
xhttp.JsonBaseResponse(w, "认证失败:"+err.Error())
}

func main() {
  /*
  ....
  */
// 在main函数中添加一个callback
 server := rest.MustNewServer(c.RestConf, rest.WithUnauthorizedCallback(unauthorizedHandler))
        // 自定义处理返回
    }))

      /*
  ....
  */
}

运行项目,再次测试认证失败的情况:
在这里插入图片描述


原文地址:https://blog.csdn.net/yang731227/article/details/143866146

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!