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。
{ "alg": "HS256", "typ": "JWT" } 复制代码
- Payload
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择
iss:发行人 exp:到期时间 sub:主题 aud:用户 nbf:在此之前不可用 iat:发布时间 jti:JWT ID用于标识该JWT 复制代码
还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:
{ "sub": "1234567890", "name": "Helen", "admin": true } 复制代码
- Signature
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的 header 和 payload 数据,通过指定的算法生成哈希,以确保数据不会被篡改。
首先,需要指定一个密钥(secret)。该密钥仅保存在服务器中,并且不能向用户公开。然后,使用 header 中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret) 复制代码
- header 和 payload 可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据
- signature 使用了不可逆的加密算法,无法解码出原文,它的作用是校验 token 有没有被篡改。
JWT 使用
下载: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 } 复制代码
路由里使用