gin博客项目复盘--05 JWT全面解读、详细使用步骤

简介: 通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,可以用于在各方之间安全地将信息作为Json对象传输。

JWT

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,可以用于在各方之间安全地将信息作为Json对象传输。

image.png

JWT 介绍

JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串。

JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
复制代码
  1. Header

JWT头是一个描述JWT元数据的JSON对象,alg 属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ 属性表示令牌的类型,JWT令牌统一写为JWT。

{
  "alg": "HS256",
  "typ": "JWT"
}
复制代码
  1. Payload

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择

iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
复制代码

还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:

{
  "sub": "1234567890",
  "name": "Helen",
  "admin": true
}
复制代码
  1. Signature

签名哈希部分是对上面两部分数据签名,需要使用base64编码后的 header 和 payload 数据,通过指定的算法生成哈希,以确保数据不会被篡改。

首先,需要指定一个密钥(secret)。该密钥仅保存在服务器中,并且不能向用户公开。然后,使用 header 中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名

HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
复制代码
  • header 和 payload 可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据
  • signature 使用了不可逆的加密算法,无法解码出原文,它的作用是校验 token 有没有被篡改。

JWT 使用

参考资料:pkg.go.dev/github.com/…

下载:go get -u github.com/dgrijalva/jwt-go

JWT中间件:middleware/jwt.go

package middleware
import (
  "errors"
  "ginVue3blog/utils"
  "ginVue3blog/utils/errmsg"
  "github.com/dgrijalva/jwt-go"
  "github.com/gin-gonic/gin"
  "net/http"
  "strings"
)
type JWT struct {
  JwtKey []byte
}
func NewJWT() *JWT {
  return &JWT{
    []byte(utils.JwtKey),
  }
}
type MyClaims struct {
  Username string `json:"username"`
  jwt.StandardClaims
}
// 定义错误
var (
  TokenExpired     error = errors.New("Token 已过期,请重新登录")
  TokenNotValidYet error = errors.New("Token 无效,请重新登录")
  TokenMalformed   error = errors.New("Token 不正确,请重新登录")
  TokenInvalid     error = errors.New("这不是一个 Token,请重新登录")
)
// CreateToken 生成token
func (j *JWT) CreateToken(claims MyClaims) (string, error) {
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  return token.SignedString(j.JwtKey)
}
// ParserToken 解析token
func (j *JWT) ParserToken(tokenString string) (*MyClaims, error) {
  token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
    return j.JwtKey, nil
  })
  if err != nil {
    if ve, ok := err.(*jwt.ValidationError); ok {
      if ve.Errors&jwt.ValidationErrorMalformed != 0 {
        return nil, TokenMalformed
      } else if ve.Errors&jwt.ValidationErrorExpired != 0 {
        return nil, TokenExpired
      } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
        return nil, TokenNotValidYet
      } else {
        return nil, TokenInvalid
      }
    }
  }
  if token != nil {
    if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
      return claims, nil
    }
    return nil, TokenInvalid
  }
  return nil, TokenInvalid
}
// JwtToken jwt中间件
func JwtToken() gin.HandlerFunc {
  return func(c *gin.Context) {
    var code int
    tokenHeader := c.Request.Header.Get("Authorization")
    if tokenHeader == "" {
      code = errmsg.ERROR_TOKEN_EXIST
      c.JSON(http.StatusOK, gin.H{
        "status":  code,
        "message": errmsg.GetErrMsg(code),
      })
      c.Abort()
      return
    }
    checkToken := strings.Split(tokenHeader, " ")
    if len(checkToken) == 0 {
      c.JSON(http.StatusOK, gin.H{
        "status":  code,
        "message": errmsg.GetErrMsg(code),
      })
      c.Abort()
      return
    }
    if len(checkToken) != 2 || checkToken[0] != "Bearer" {
      c.JSON(http.StatusOK, gin.H{
        "status":  code,
        "message": errmsg.GetErrMsg(code),
      })
      c.Abort()
      return
    }
    j := NewJWT()
    //解析token
    claims, err := j.ParserToken(checkToken[1])
    if err != nil {
      if err == TokenExpired {
        c.JSON(http.StatusOK, gin.H{
          "status":  errmsg.ERROR,
          "message": "token授权已过期,请重新登录",
          "data":    nil,
        })
        c.Abort()
        return
      }
      //其他错误
      c.JSON(http.StatusOK, gin.H{
        "status":  errmsg.ERROR,
        "message": err.Error(),
        "data":    nil,
      })
      c.Abort()
      return
    }
    c.Set("username", claims)
    c.Next()
  }
}
复制代码

登录 api/v1/login.go

package v1
import (
  "ginVue3blog/middleware"
  "ginVue3blog/model"
  "ginVue3blog/utils/errmsg"
  "github.com/dgrijalva/jwt-go"
  "github.com/gin-gonic/gin"
  "net/http"
  "time"
)
// Login 后台登陆
func Login(c *gin.Context) {
  var formData model.User
  _ = c.ShouldBindJSON(&formData)
  var token string
  var code int
  formData, code = model.CheckLogin(formData.Username, formData.Password)
  if code == errmsg.SUCCSE {
    setToken(c, formData)
  } else {
    c.JSON(http.StatusOK, gin.H{
      "status":  code,
      "data":    formData.Username,
      "id":      formData.ID,
      "message": errmsg.GetErrMsg(code),
      "token":   token,
    })
  }
}
// LoginFront 前台登录
func LoginFront(c *gin.Context) {
  var formData model.User
  _ = c.ShouldBindJSON(&formData)
  var code int
  formData, code = model.CheckLoginFront(formData.Username, formData.Password)
  c.JSON(http.StatusOK, gin.H{
    "status":  code,
    "data":    formData.Username,
    "id":      formData.ID,
    "message": errmsg.GetErrMsg(code),
  })
}
// token生成函数
func setToken(c *gin.Context, user model.User) {
  j := middleware.NewJWT()
  claims := middleware.MyClaims{
    Username: user.Username,
    StandardClaims: jwt.StandardClaims{
      NotBefore: time.Now().Unix() - 100,
      ExpiresAt: time.Now().Unix() + 604800,
      Issuer:    "GinBlog",
    },
  }
  token, err := j.CreateToken(claims)
  if err != nil {
    c.JSON(http.StatusOK, gin.H{
      "status":  errmsg.ERROR,
      "message": errmsg.GetErrMsg(errmsg.ERROR),
      "token":   token,
    })
  }
  c.JSON(http.StatusOK, gin.H{
    "status":  200,
    "data":    user.Username,
    "id":      user.ID,
    "message": errmsg.GetErrMsg(200),
    "token":   token,
  })
  return
}
复制代码


路由里使用

image.png

相关文章
|
3月前
|
存储 JSON JavaScript
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)-1
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)
121 0
|
12天前
|
关系型数据库 API Go
[golang]在Gin框架中使用JWT鉴权
[golang]在Gin框架中使用JWT鉴权
|
13天前
|
存储 JSON 安全
在项目中到底应不应该用jwt?
JSON Web Tokens(JWT)是一种开放标准,用于在网络上传输安全信息。它常用于身份验证场景,用户登录后,服务器生成JWT并返回给客户端。客户端在后续请求中携带此令牌,服务器验证其有效性来确认用户身份。JWT具有无状态、可扩展和安全的特点,支持跨域认证,但也有令牌大小、续期复杂等缺点。是否使用JWT取决于项目需求,多数公司在采用,除非有特殊理由避免。以下是Go语言中使用JWT的一个示例。
35 0
|
9月前
|
JSON 前端开发 安全
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)-2
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)
70 0
|
3月前
|
JSON 前端开发 安全
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)-2
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)
89 0
|
3月前
|
JSON 算法 前端开发
gin框架JWT验证实践(原理介绍,代码实践)
gin框架JWT验证实践(原理介绍,代码实践)
121 0
|
8月前
|
JSON 数据格式
jwt->jwt简介,jwt工具类,jwt集进成spa项目
jwt->jwt简介,jwt工具类,jwt集进成spa项目
55 0
|
9月前
|
存储 JSON JavaScript
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)-1
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)
86 0
|
9月前
|
JSON JavaScript 算法
[Vue]之Jwt的入门和Jwt工具类的使用及Jwt集成spa项目
[Vue]之Jwt的入门和Jwt工具类的使用及Jwt集成spa项目
124 0
|
9月前
|
算法 Java 数据安全/隐私保护
微服务轮子项目(20) -JWT的RSA非对称密钥生成
微服务轮子项目(20) -JWT的RSA非对称密钥生成
41 0