在实现了资产管理系统大体的业务之后,发现用户的鉴权可以直接使用jwt。下面简单介绍以下go-jwt和我在资产管理系统中是如何落地jwt的。
jwt介绍
JSON Web Token(JWT)是一种用于在网络应用间传递信息的开放标准。它是一种轻量级的数据交换格式,通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。
1. 头部(Header): 头部通常由两部分组成:令牌的类型(即 "typ" 字段,通常为 "JWT")和使用的签名算法(即 "alg" 字段,例如 "HS256" 表示使用 HMAC-SHA256 算法)。
{
"alg": "HS256",
"typ": "JWT"
}
2. 载荷(Payload): 载荷是 JWT 的第二部分,包含要传递的数据。载荷可以包含称为 "claims" 的声明,这些声明描述了关于实体(通常是用户)和其他数据的一些信息。有三种类型的声明:注册声明、公开声明和私有声明。注册声明是预定义的,用于标识 JWT 的信息,例如 "iss"(签发者)、"exp"(过期时间)等。公开声明可以自由定义。私有声明用于在双方之间交换信息,通常不会被标准规范使用。
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
3. 签名(Signature): JWT 的签名部分使用头部和载荷,以及一个密钥,通过指定的签名算法生成。签名的目的是验证发送者是否是该令牌的有效签发者,以及确保在传输过程中数据没有被篡改。
生成签名的过程:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
最终生成的 JWT 结构:base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + signature
go-jwt的实践
首先
go get github.com/appleboy/gin-jwt/v2
然后这样引入
import(
...
jwt "github.com/appleboy/gin-jwt/v2"
)
下面是这个中间件使用的全部代码,看不懂没关系,我会慢慢解释。
package middleware
import (
"log"
"msbams/src/dbconn"
"msbams/src/entity"
"msbams/src/gen/model"
"time"
jwt "github.com/appleboy/gin-jwt/v2"
"github.com/gin-gonic/gin"
)
var IdentityKey = "username"
var AuthMiddleware *jwt.GinJWTMiddleware
type AccountForJwt struct {
Username string
Name string
Id int32
Type int32
}
func init() {
// the jwt middleware
var err error
AuthMiddleware, err = jwt.New(&jwt.GinJWTMiddleware{
Realm: "test zone",
Key: []byte("msbams.,."),
Timeout: time.Hour,
MaxRefresh: time.Hour,
IdentityKey: IdentityKey,
PayloadFunc: func(data interface{}) jwt.MapClaims {
// fmt.Println("PayloadFunc", data)
if v, ok := data.(*AccountForJwt); ok { // 登录成功后,会调用这个函数
return jwt.MapClaims{
IdentityKey: v.Username,
"id": v.Id,
"name": v.Name,
"type": v.Type,
}
}
return jwt.MapClaims{}
},
IdentityHandler: func(c *gin.Context) interface{} { //鉴权的时候会自动调用这个
// fmt.Println("IdentityHandler")
claims := jwt.ExtractClaims(c)
return &AccountForJwt{
Username: claims[IdentityKey].(string),
Id: int32(claims["id"].(float64)),
Name: claims["name"].(string),
Type: int32(claims["type"].(float64)),
}
},
Authenticator: func(c *gin.Context) (interface{}, error) {
login_req := entity.AccountRequest{}
err := c.BindJSON(&login_req)
if err != nil || login_req.Password == "" || login_req.Username == "" {
return "", jwt.ErrMissingLoginValues
}
acc := model.Account{}
res := dbconn.DB.Where("username = ?", login_req.Username).First(&acc)
if res.Error != nil {
return nil, jwt.ErrFailedAuthentication
}
if acc.Password == login_req.Password {
// fmt.Println("login success", acc)
return &AccountForJwt{
Username: acc.Username,
Name: acc.Name,
Id: acc.ID,
Type: acc.Type,
}, nil
}
return nil, jwt.ErrFailedAuthentication
},
Authorizator: func(data interface{}, c *gin.Context) bool {
// fmt.Println("Authorizator")
if v, ok := data.(*AccountForJwt); ok && v.Username != "" {
return true
}
return false
},
Unauthorized: func(c *gin.Context, code int, message string) {
c.JSON(code, entity.Result{
Code: code,
Description: message,
})
},
TokenLookup: "header: Authorization, query: token, cookie: token",
TokenHeadName: "Bearer",
TimeFunc: time.Now,
})
if err != nil {
log.Fatal("JWT Error:" + err.Error())
}
errInit := AuthMiddleware.MiddlewareInit()
if errInit != nil {
panic("authMiddleware.MiddlewareInit() Error:" + errInit.Error())
}
}
这段代码实现了基于 JWT 的身份验证和授权,它会在用户登录成功后生成一个 JWT 令牌,并在每次请求时验证令牌的有效性,以确定用户是否有权限访问特定资源。如果令牌有效且用户有权限,则允许用户访问资源。否则,返回未授权错误403。
emmm那么我们如何在service层中使用呢?很简单:
user, _ := c.Get(middleware.IdentityKey)
accountId := user.(*middleware.AccountForJwt).Id
即可。其中middleware是你的中间件所在的的包名。
如何实现它作为中间件的价值呢?只需要调用路由的Use函数即可:
//采购相关
purchaseGroup := r.Group("/purchase")
purchaseGroup.Use(middleware.AuthMiddleware.MiddlewareFunc())