golang,jwt-go实现生成token,中间件验证token
2023-02-26 15:18 糯米粥 阅读(1412) 评论(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的一个过程