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有三种加密方式分别是 :SigningMethodHS256
、SigningMethodHS384
、SigningMethodHS512
,这里我们用的是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)!