代码改变世界

golang,jwt-go实现生成token,中间件验证token

2023-02-26 15:18  糯米粥  阅读(1282)  评论(0编辑  收藏  举报

前后端分离的项目。现在基本上都是JWT

在go中通过https://github.com/dgrijalva/jwt-go 可以实现token的创建也解析

注意:因为是案例,所以代码中很多配置是写死的,正常开发肯定是读取配置

package main

import (
    "fmt"
    "github.com/dgrijalva/jwt-go"
    "godemo/token/jwt"
    "io"
    "os"
    "time"
)

const secretkey = "1234567890abcdefghijk"

// 自定义类
type CustomClaims struct {
    UserId   int64
    UserName string
    jwt.StandardClaims
}
// 创建token
func CreateToken() (string, error) {
    customClaims := &CustomClaims{
        UserId:   110,
        UserName: "tom",
        StandardClaims: jwt.StandardClaims{
            Audience:  "app项目",                                             //颁发给谁,就是使用的一方
            ExpiresAt: time.Now().Add(time.Duration(time.Hour * 1)).Unix(), //过期时间
            //Id:        "",//非必填
            IssuedAt: time.Now().Unix(), //颁发时间
            Issuer:   "博客园",             //颁发者
            //NotBefore: 0,         //生效时间,就是在这个时间前不能使用,非必填
            Subject: "111",
        },
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, customClaims)
    tokenString, err := token.SignedString([]byte(secretkey))
    if err != nil {
        return "", err
    }
    //defer func() {
    //    e := recover()
    //    if e != nil {
    //        panic(e)
    //    }
    //}()

    return tokenString, nil
}

// 解析token
func ParseToken(tokenString string) (*CustomClaims, error) {
    /*
        jwt.ParseWithClaims有三个参数
        第一个就是加密后的token字符串
        第二个是Claims, &CustomClaims 传指针的目的是。然让它在我这个里面写数据
        第三个是一个自带的回调函数,将秘钥和错误return出来即可,要通过密钥解密
    */
    token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
        //if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
        //    return nil, fmt.Errorf("算法类型: %v", token.Header["alg"])
        //}
        return []byte(secretkey), nil
    })
    if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
        //a := token.Valid
        //b := claims.Valid()
        //fmt.Println(a, b)
        return claims, nil
    } else {
        return nil, err
    }
}

 

当然,你也可以通过RSA的方式,算法是:jwt.SigningMethodRS512

 

首先通过网站:https://cryptotools.net/rsagen 生成key,该网站是本地生成,不会保存到服务器,可以放心使用

 

 

 根据长度,生成key

 

 

 

//RSA方式
func
RSACreateToken() (string, error) { //读取key pkFile, err := os.Open("token/config/private.key") if err != nil { } //ioutil.ReadAll() pkBytes, err := io.ReadAll(pkFile) //u := string(pkBytes) //fmt.Printf("%s", pkBytes)
//pubKey, err := jwt.ParseRSAPublicKeyFromPEM(pkBytes)

privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(pkBytes) //fmt.Println(privateKey) customClaims := &CustomClaims{ UserId: 110, UserName: "tom", StandardClaims: jwt.StandardClaims{ Audience: "app项目", //颁发给谁,就是使用的一方 ExpiresAt: time.Now().Add(time.Duration(time.Hour * -1)).Unix(), //过期时间 //Id: "",//非必填 IssuedAt: time.Now().Unix(), //颁发时间 Issuer: "博客园", //颁发者 //NotBefore: 0, //生效时间,就是在这个时间前不能使用,非必填 Subject: "111", }, } token := jwt.NewWithClaims(jwt.SigningMethodRS512, customClaims) tokenString, err := token.SignedString(privateKey) if err != nil { return "1", err } //defer func() { // e := recover() // if e != nil { // panic(e) // } //}() return tokenString, nil }

解密也是同样的道理,其余地方没区别

解密读取publickey ,

pubKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(b))

rsa可以验证数据是否更改过。比如一个摘要,通过privatekey加密,必须通过publickey 解密,比如服务器发给app的消息,和其他人伪装消息发给app,这个时候app只能解密服务器发的消息

jwt.ParseWithClaims(token, &CustomClaims{}, func(t *jwt.Token) (interface{}, error) {
return PublicKey, nil
})

 

既然token有了。那可以试试在后端接受token,并判断

这里使用gin框架,https://github.com/gin-gonic/gin

中文文档:https://gin-gonic.com/zh-cn/

我这里模拟登录获取token,然后获取用户信息,但用户信息需要登录授权,也就是需要携带token

定义认证中间件:

func VerAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        //从head中获取token
        aut := c.Request.Header.Get("Authorization")
        //没有携带token,或者token格式不对
        if ok := strings.HasPrefix(aut, "Bearer "); aut == "" || !ok {
            c.JSON(http.StatusUnauthorized, nil)
            c.Abort() //终止
            return
        }
        token := aut[len("Bearer "):]
        if token == "" {
            c.JSON(http.StatusUnauthorized, nil)
        }

        //验证token是否合法
        customClaims, err := ParseToken(token)
        if err != nil {
            c.JSON(http.StatusUnauthorized, "没有登录")
            c.Abort()
            return
        }
        c.Set("claims", customClaims) //保存到上下文
        c.Next()                      //继续走后面的逻辑
    }
}

 

定义获取用户信息的方法


type user struct {
UserId int64
Name string
Age int
Address string
}
func GetUser(c *gin.Context) {
    //if value, exists := c.Get("claims"); exists {
    //    //claims 不存在
    //}
    value, exists := c.Get("claims") //判断claims是是否存在
    if !exists {
        //不存在,做其他逻辑
    }
    claims := value.(*CustomClaims) //可以理解为类型匹配,就是类型转换
//
claims,ok := value.(*CustomClaims) //这样ok就可以判断转换是否成功

c.JSONP(http.StatusOK, user{ UserId: claims.UserId,
//从token中获取用户id Name: "Tom", Age: 18, Address: "地球人", }) }

 

验证token

这里可以通过具体的error信息,判断详细信息,比如

ValidationErrorMalformed 说明是无效的令牌

 

 

func ParseToken(tokenString string) (*CustomClaims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(tkn *jwt.Token) (interface{}, error) {
        return []byte(secretkey), nil
    })
    if err != nil {
        //可以判断是什么原因导致的token不正确,当然,统一返回的消息,都是token无效
        if v, ok := err.(*jwt.ValidationError); ok {
            if v.Errors&jwt.ValidationErrorMalformed != 0 { // 令牌不完整,就是说不是有效的jwt token 格式
                return nil, errors.New("令牌不完整")
            } else if v.Errors&jwt.ValidationErrorExpired != 0 { // 令牌过期
                return nil, errors.New("令牌过期")
            } else if v.Errors&jwt.ValidationErrorNotValidYet != 0 { // 令牌还未生效
                return nil, errors.New("令牌还未生效")
            } else {
                return nil, errors.New("无效")
            }
        }
        //return nil, nil
    }
    if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
        return claims, nil
    }
    return nil, nil
}

 

编写gin代码

func main() {
    r := gin.Default()
    r.GET("/user", VerAuth(), GetUser) //注意,中间件顺序
    r.POST("/login", func(context *gin.Context) {
        token, err := CreateToken()
        if err != nil {
            //异常
        }
        context.JSONP(http.StatusOK, gin.H{
            "token": token,
        })
    })
    r.Run()
}

 

通过postman请求

先尝试没有登录的情况,获取用户信息

 

 

1:获取token

 

 

2:携带token获取用户信息

 

 

这样就完成的从获取token,到认证token的一个过程