鉴权类及方法
package init
import (
"errors"
"fmt"
"gin/global"
"gin/utils"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"net/http"
"time"
)
type JWT struct {
// 声明签名信息
Secret []byte
}
// NewJWT 初始化JWT对象
func NewJWT() *JWT {
return &JWT{
Secret: []byte(global.Settings.JwtConfig.Secret),
}
}
// CustomClaims 自定义有效载荷
type CustomClaims struct {
Name string `json:"name"`
UserId string `json:"id"`
jwt.StandardClaims // StandardClaims结构体实现了Claims接口(Valid()函数)
}
// CreateToken 调用jwt-go库生成token
func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
// 指定编码算法为jwt.SigningMethodHS256
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 返回一个token结构体指针
return token.SignedString(j.Secret)
}
// ParserToken token解码
func (j *JWT) ParserToken(tokenString string) (*CustomClaims, error) {
// 输入用户自定义的Claims结构体对象,token,以及自定义函数来解析token字符串为jwt的Token结构体指针
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return j.Secret, nil
})
if err != nil {
var ve *jwt.ValidationError
if errors.As(err, &ve) { // jwt.ValidationError:是一个无效token的错误结构
if ve.Errors&jwt.ValidationErrorMalformed != 0 { // ValidationErrorMalformed是一个uint常量,表示token不可用
return nil, fmt.Errorf("token不可用")
} else if ve.Errors&jwt.ValidationErrorExpired != 0 { // ValidationErrorExpired表示Token过期
return nil, fmt.Errorf("token过期")
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { // ValidationErrorNotValidYet表示无效token
return nil, fmt.Errorf("无效的token")
} else {
return nil, fmt.Errorf("token不可用")
}
}
return nil, err
}
// 将token中的claims信息解析出来并断言成用户自定义的有效载荷结构
claims, ok := token.Claims.(*CustomClaims)
if ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("token不可用")
}
// JWTAuth 定义一个JWTAuth的中间件
func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 通过http header中的token解析来认证
token := c.Request.Header.Get("token")
if token == "" {
utils.ReturnError(c, http.StatusForbidden, "请求未携带token,无权访问!")
c.Abort() // Abort 函数在被调用的函数中阻止后续中间件的执行(这里都没有携带token,后续就不用执行了)
return
}
j := NewJWT() // 初始化一个JWT对象实例,并根据结构体方法来解析token
claims, err := j.ParserToken(token) // 解析token中包含的相关信息(有效载荷)
if err != nil {
utils.ReturnError(c, http.StatusForbidden, err.Error())
c.Abort()
return
}
if redistoken, _ := global.Redis.Get(claims.UserId).Result(); redistoken != "" {
if redistoken == token {
if claims.Name != global.Settings.JwtConfig.Super {
url := c.Request.URL.RequestURI()
var total int64
//鉴权 具体逻辑代码可根据自己的改写
global.DB.Raw("select * from v_user_func where url=? and username=?", url, claims.UserId).Scan(&total)
if total == 0 {
utils.ReturnError(c, http.StatusForbidden, "您没有该权限")
}
}
//若在到期之前时间段请求可刷新token
if claims.ExpiresAt-time.Now().Unix() < global.Settings.JwtConfig.RefreshTokenPeriod {
c.Header("refresh_token", GetToken(claims.Name, claims.UserId))
}
// 将解析后的有效载荷claims重新写入gin.Context引用对象中(gin的上下文)
c.Set("claims", claims)
} else {
utils.ReturnError(c, http.StatusForbidden, "token已刷新,请使用最新token")
c.Abort()
return
}
} else {
utils.ReturnError(c, http.StatusForbidden, "token不可用")
c.Abort()
return
}
}
}
func GetToken(use string, userid string) string {
j := NewJWT() // 构造SignKey: 签名和解签名需要使用一个值
claims := CustomClaims{ // 构造用户claims信息(负载)
Name: use,
UserId: userid,
StandardClaims: jwt.StandardClaims{
NotBefore: int64(time.Now().Unix()), // 签名生效时间秒
ExpiresAt: int64(time.Now().Unix() + global.Settings.JwtConfig.Expire), // 签名过期时间秒
Issuer: global.Settings.JwtConfig.Secret, // 签名颁发者
},
}
token, err := j.CreateToken(claims) // 根据claims生成token对象
if err != nil {
panic(err)
}
//存储具体使用看个人的逻辑
//存redis
result, err := global.Redis.Set(userid, token, time.Duration(global.Settings.JwtConfig.Expire)*time.Second).Result()
fmt.Println(result)
return token
}
使用
package router
import (
"gin/controller"
"gin/init"
"github.com/gin-gonic/gin"
)
func Router() *gin.Engine {
all := r.Group("").Use(init.JWTAuth())
{
all.GET("/test",controller.TestController{}.Test)
}
读取yaml文件去看这篇
jwt:
secret: 'xxx'
#刷新时间 秒
refresh_token_period: 1800
#过期时间 秒
expire: 3600
super: admin