JWT
❝通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,可以用于在各方之间安全地将信息作为Json对象传输。 ❞
JWT 介绍
JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.
进行连接形成最终传输的字符串。
JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header) "." base64UrlEncode(payload),secret)
- Header
JWT头是一个描述JWT元数据的JSON对象,alg 属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ 属性表示令牌的类型,JWT令牌统一写为JWT。
代码语言:javascript复制{
"alg": "HS256",
"typ": "JWT"
}
- Payload
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。JWT指定七个默认字段供选择
代码语言:javascript复制iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:
代码语言:javascript复制{
"sub": "1234567890",
"name": "Helen",
"admin": true
}
- Signature
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的 header 和 payload 数据,通过指定的算法生成哈希,以确保数据不会被篡改。
首先,需要指定一个密钥(secret)。该密钥仅保存在服务器中,并且不能向用户公开。然后,使用 header 中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
代码语言:javascript复制HMACSHA256(base64UrlEncode(header) "." base64UrlEncode(payload),secret)
- header 和 payload 可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据
- signature 使用了不可逆的加密算法,无法解码出原文,它的作用是校验 token 有没有被篡改。
JWT 使用
参考资料:https://pkg.go.dev/github.com/dgrijalva/jwt-go/v4
下载:go get -u github.com/dgrijalva/jwt-go
JWT中间件:middleware/jwt.go
代码语言:javascript复制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
代码语言:javascript复制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,
})
}
}
// 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
}
路由里使用
图片及部分相关技术知识点来源于网络搜索,侵权删!