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

导入包

import "github.com/dgrijalva/jwt-go"

为了简化操作,我们直接在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))
        // 自定义处理返回
    }))

      /*
  ....
  */
}

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

Go-Zero框架中集成JWT(JSON Web Token)进行身份验证通常包含以下几个步骤: 1. **安装依赖**: 首先需要安装`github.com/dgrijalva/jwt-go`库,它是一个轻量级的JWT处理包。可以使用go get命令安装: ``` go get github.com/dgrijalva/jwt-go ``` 2. **设置JWT密钥**: 创建一个私钥(secret key),这个密钥将在签发JWT时使用。例如,你可以将其作为环境变量存储: ```bash export JWT_SECRET_KEY="your-secret-key" ``` 3. **创建认证中间件**: 在Go-Zero应用中创建一个新的中间件函数,该函数会在每个请求之前检查JWT。这里有一个简单的示例: ```go import ( "context" "github.com/gin-gonic/gin" jwt "github.com/dgrijalva/jwt-go" ) func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { tokenString := c.GetHeader("Authorization") if tokenString == "" { c.AbortWithStatus(http.StatusUnauthorized) return } // 解析token token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return []byte(os.Getenv("JWT_SECRET_KEY")), nil }) if err != nil { c.AbortWithStatus(http.StatusUnauthorized) return } // 检查token是否有效 if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { // 在这里添加对claims的验证,并把验证后的用户信息保存到上下文中 user := claims["sub"].(string) ctx := context.WithValue(c.Request.Context(), "user", user) c.SetContext(ctx, c) next(c) } else { c.AbortWithStatus(http.StatusUnauthorized) } } } ``` 4. **保护路由**: 将AuthMiddleware应用于你需要授访问的路由前: ```go router.GET("/protected", AuthMiddleware, yourController.MethodThatNeedsAuth) ``` 5. **刷新令牌**(可选): 如果需要提供token刷新功能,可以在成功后返回一个有效期更长的新token。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值