jwt2
我们先定义一个用于jwt认证的中间件.
// 定义一个JWTAuth的中间件
func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 通过http header中的token解析来认证
token := c.Request.Header.Get("token")
if token == "" {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": "请求未携带token,无权限访问",
"data": nil,
})
c.Abort()
return
}
log.Print("get token: ", token)
// 初始化一个JWT对象实例,并根据结构体方法来解析token
j := NewJWT()
// 解析token中包含的相关信息(有效载荷)
claims, err := j.ParserToken(token)
if err != nil {
// token过期
if err == TokenExpired {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": "token授权已过期,请重新申请授权",
"data": nil,
})
c.Abort()
return
}
// 其他错误
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": err.Error(),
"data": nil,
})
c.Abort()
return
}
// 将解析后的有效载荷claims重新写入gin.Context引用对象中
c.Set("claims", claims)
}
}
定义jwt编码和解码逻辑 根据前面提到的jwt-token的组成部分,以及jwt-go中相关的定义,我们可以使用如下方法进行生成token.
// 定义一个jwt对象
type JWT struct {
// 声明签名信息
SigningKey []byte
}
// 初始化jwt对象
func NewJWT() *JWT {
return &JWT{
[]byte("bgbiao.top"),
}
}
// 自定义有效载荷(这里采用自定义的Name和Email作为有效载荷的一部分)
type CustomClaims struct {
Name string `json:"name"`
Email string `json:"email"`
// StandardClaims结构体实现了Claims接口(Valid()函数)
jwt.StandardClaims
}
// 调用jwt-go库生成token
// 指定编码的算法为jwt.SigningMethodHS256
func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
// https://gowalker.org/github.com/dgrijalva/jwt-go#Token
// 返回一个token的结构体指针
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(j.SigningKey)
}
// token解码
func (j *JWT) ParserToken(tokenString string) (*CustomClaims, error) {
// https://gowalker.org/github.com/dgrijalva/jwt-go#ParseWithClaims
// 输入用户自定义的Claims结构体对象,token,以及自定义函数来解析token字符串为jwt的Token结构体指针
// Keyfunc是匿名函数类型: type Keyfunc func(*Token) (interface{}, error)
// func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {}
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return j.SigningKey, nil
})
if err != nil {
// https://gowalker.org/github.com/dgrijalva/jwt-go#ValidationError
// jwt.ValidationError 是一个无效token的错误结构
if ve, ok := err.(*jwt.ValidationError); ok {
// ValidationErrorMalformed是一个uint常量,表示token不可用
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, fmt.Errorf("token不可用")
// ValidationErrorExpired表示Token过期
} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
return nil, fmt.Errorf("token过期")
// ValidationErrorNotValidYet表示无效token
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
return nil, fmt.Errorf("无效的token")
} else {
return nil, fmt.Errorf("token不可用")
}
}
}
// 将token中的claims信息解析出来并断言成用户自定义的有效载荷结构
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("token无效")
}
定义登陆验证逻辑 接下来的部分就是普通api的具体逻辑了,比如可以在登陆时进行用户校验,成功后未该次认证请求生成token。
// 定义登陆逻辑
// model.LoginReq中定义了登陆的请求体(name,passwd)
func Login(c *gin.Context) {
var loginReq model.LoginReq
if c.BindJSON(&loginReq) == nil {
// 登陆逻辑校验(查库,验证用户是否存在以及登陆信息是否正确)
isPass, user, err := model.LoginCheck(loginReq)
// 验证通过后为该次请求生成token
if isPass {
generateToken(c, user)
} else {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": "验证失败" + err.Error(),
"data": nil,
})
}
} else {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": "用户数据解析失败",
"data": nil,
})
}
}
// token生成器
// md 为上面定义好的middleware中间件
func generateToken(c *gin.Context, user model.User) {
// 构造SignKey: 签名和解签名需要使用一个值
j := md.NewJWT()
// 构造用户claims信息(负荷)
claims := md.CustomClaims{
user.Name,
user.Email,
jwtgo.StandardClaims{
NotBefore: int64(time.Now().Unix() - 1000), // 签名生效时间
ExpiresAt: int64(time.Now().Unix() + 3600), // 签名过期时间
Issuer: "bgbiao.top", // 签名颁发者
},
}
// 根据claims生成token对象
token, err := j.CreateToken(claims)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": err.Error(),
"data": nil,
})
}
log.Println(token)
// 封装一个响应数据,返回用户名和token
data := LoginResult{
Name: user.Name,
Token: token,
}
c.JSON(http.StatusOK, gin.H{
"status": 0,
"msg": "登陆成功",
"data": data,
})
return
}
定义普通待验证接口
// 定义一个普通controller函数,作为一个验证接口逻辑
func GetDataByTime(c *gin.Context) {
// 上面我们在JWTAuth()中间中将'claims'写入到gin.Context的指针对象中,因此在这里可以将之解析出来
claims := c.MustGet("claims").(*md.CustomClaims)
if claims != nil {
c.JSON(http.StatusOK, gin.H{
"status": 0,
"msg": "token有效",
"data": claims,
})
}
}
// 在主函数中定义路由规则
router := gin.Default()
v1 := router.Group("/apis/v1/")
{
v1.POST("/register", controller.RegisterUser)
v1.POST("/login", controller.Login)
}
// secure v1
sv1 := router.Group("/apis/v1/auth/")
// 加载自定义的JWTAuth()中间件,在整个sv1的路由组中都生效
sv1.Use(md.JWTAuth())
{
sv1.GET("/time", controller.GetDataByTime)
}
router.Run(":8081")