JWT身份認證(附帶源碼講解)

[TOC]

JWT(Json Web Token)驗證(附帶源碼講解)

一天,正是午休時段

兵長路過胖sir座位炼七,大吃一驚,今天胖sir居然沒有打呼嚕蓖康,而是在低著頭聚精會神盯著一本書

兵長湊近一看筐眷,胖sir居然在看史書...

兵長:(輕聲道)黎烈,你在看~~ 什 ~~ 么 ~~

胖sir:我在想我要是穿越到清朝,我會是啥身份匀谣?

what照棋??~~~ 振定, 能是啥身份必怜,肯定是重量級人物唄

胖sir: 我呸, 今天我倒要給你講講啥叫身份

講到身份后频,不得不說一下cookie梳庆、session、Token的區(qū)別卑惜,come on

1 cookie膏执、session、Token的區(qū)別

Cookie

Cookie總是保存在客戶端中露久,按在客戶端中的存儲位置更米,可分為 內(nèi)存Cookie硬盤Cookie

內(nèi)存Cookie由瀏覽器維護毫痕,保存在內(nèi)存中征峦,瀏覽器關閉后就消失了,其存在時間是短暫的消请。

硬盤Cookie保存在硬盤?栏笆,有?個過期時間,除??戶??清理或到了過期時間臊泰,硬盤Cookie不會被刪除蛉加,其存在時間 是?期的。

所以,按存在時間针饥,可分為 ?持久Cookie持久Cookie厂抽。

那么cookies到底是什么呢?

cookie 是?個?常具體的東?丁眼,指的就是瀏覽器??能永久存儲的?種數(shù)據(jù)筷凤,僅僅是瀏覽器實現(xiàn)的?種數(shù)

據(jù)存儲功能。

cookie由服務器?成户盯,發(fā)送給瀏覽器 嵌施,瀏覽器把cookie以key-value形式保存到某個?錄下的?本?件

內(nèi),下?次請求同??站時會把該cookie發(fā)送給服務器莽鸭。由于cookie是存在客戶端上的吗伤,所以瀏覽器加?

了?些限制確保cookie不會被惡意使?,同時不會占據(jù)太多磁盤空間硫眨,所以每個域的cookie數(shù)量是有限的足淆。

Session

Session字?意思是會話,主要?來標識??的身份礁阁。

?如在?狀態(tài)的api服務在多次請求數(shù)據(jù)庫時巧号,如何 知道是同?個?戶,這個就可以通過session的機制姥闭,服務器要知道當前發(fā)請求給??的是誰丹鸿,為了區(qū)分客戶端請求, 服務端會給具體的客戶端?成身份標識session 棚品,然后客戶端每次向服務器發(fā)請求 的時候靠欢,都帶上這個“身份標識”,服務器就知道這個請求來?于誰了铜跑。

?于客戶端如何保存該標識门怪,可以有很多?式,對于瀏覽器??锅纺,?般都是使? cookie 的?式 掷空,服務器使?session把?戶信息臨時保存了服務器上,?戶離開?站就會銷毀囤锉,這種憑證存儲?式相對于 坦弟,cookie來說更加安全。

但是session會有?個缺陷: 如果web服務器做了負載均衡官地,那么下?個操作請求到 了另?臺服務器的時候session會丟失酿傍。

因此厢汹,通常企業(yè)?會使? redis,memcached 緩存中間件來實現(xiàn)session的共享,此時web服務器就是? 個完全?狀態(tài)的存在凄鼻,所有的?戶憑證可以通過共享session的?式存取命辖,當前session的過期和銷毀機制 需要?戶做控制耳幢。

Token

token的意思是“令牌”爸邢,是?戶身份的驗證?式科乎,最簡單的token組成: uid(?戶唯?標識) + time(當前 時間戳) + sign(簽名,由token的前?位+鹽以哈希算法壓縮成?定?度的?六進制字符串) 密强,同時還可 以將不變的參數(shù)也放進token

這里說的token只的是 JWT(Json Web Token)

2 JWT是個啥宴杀?

?般??癣朗,?戶注冊登陸后會?成?個jwt token返回給瀏覽器,瀏覽器向服務端請求數(shù)據(jù)時攜帶 token 旺罢,服務器端使? signature 中定義的?式進?解碼旷余,進?對token進?解析和驗證。

jwt token 的組成部分

image
  • header: ?來指定使?的算法(HMAC SHA256 RSA)和token類型(如JWT)

    官網(wǎng)上可以找到各種語言的jwt庫扁达,例如我們下面使用這個庫進行編碼正卧,因為這個庫使用的人是最多的,值得信賴

    go get github.com/dgrijalva/jwt-go
    
    image
  • payload: 包含聲明(要求)跪解,聲明通常是?戶信息或其他數(shù)據(jù)的聲明炉旷,?如?戶id,名稱叉讥,郵箱等. 聲明窘行。可分為三種: registered,public,private

  • signature: ?來保證JWT的真實性图仓,可以使?不同的算法

header

token的第一部分罐盔,如

{
  "alg": "HS256",
  "typ": "JWT"
}

對上?的json進?base64編碼即可得到JWT的第?個部分

payload

token第二部分如

  • registered claims: 預定義的聲明,通常會放置?些預定義字段救崔,?如過期時間惶看,主題等(iss:issuer,exp:expiration time,sub:subject,aud:audience)

  • public claims: 可以設置公開定義的字段

  • private claims: ?于統(tǒng)?使?他們的各?之間的共享信息

不要在header和payload中放置敏感信息,除?信息本身已經(jīng)做過脫敏處理帚豪,因為payload部分的具體數(shù)據(jù)是可以通過token來獲取到的

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

Signature

token的第三部分

為了得到簽名部分碳竟,必須有編碼過的header和payload,以及?個秘鑰狸臣,簽名算法使?header中指定的那 個莹桅,然后對其進?簽名即可

HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

簽名是 ?于驗證消息在傳遞過程中有沒有被更改 ,并且烛亦,對于使?私鑰簽名的token诈泼,它還可以驗證JWT

的發(fā)送?是否為它所稱的發(fā)送?。

image

簽名的?的

最后?步簽名的過程煤禽,實際上是對頭部以及載荷內(nèi)容進?簽名铐达。

?般??,加密算法對于不同的輸? 產(chǎn)?的輸出總是不?樣的檬果。對于兩個不同的輸?瓮孙,產(chǎn)?同樣的輸出的概率極其地?唐断。所以,我們就把“不?樣的輸?產(chǎn)?不?樣的輸出”當做必然事件來看待杭抠。

所以脸甘,如果有?對頭部以及載荷的內(nèi)容解碼之后進?修改,再進?編碼的話偏灿,那么新的頭部和載荷的 簽名和之前的簽名就將是不?樣的丹诀。?且,如果不知道服務器加密的時候?的密鑰的話翁垂,得出來的簽名也 ?定會是不?樣的铆遭。

image

服務器應?在接受到JWT后,會?先對頭部和載荷的內(nèi)容?同?算法再次簽名沿猜。那么服務器應?是怎 么知道我們?的是哪?種算法呢枚荣?

在JWT的頭部中已經(jīng)?alg字段指明了我們的加密算法了

如果服務器應?對頭部和載荷再次以同樣?法簽名之后發(fā)現(xiàn)邢疙,??計算出來的簽名和接受到的簽名不 ?樣棍弄,那么就說明這個Token的內(nèi)容被別?動過的,我們應該拒絕這個Token疟游,

注意:在JWT中呼畸,不應該在載荷??加?任何敏感的數(shù)據(jù),?如?戶的密碼颁虐。具體原因上文已經(jīng)給過答案了

jwt.io?站

在jwt.io(https://jwt.io/#debugger-io)?站中蛮原,提供了?些JWT token的編碼,驗證以及?成jwt的?具另绩。

下圖就是?個典型的jwt-token的組成部分儒陨。

image-20210328145831017

啥時候使用JWT呢?

我們要明白的時候笋籽,JWT是用作認證的蹦漠,而不是用來做授權的。明白他的功能车海,那么對應JWT的應用場景就不言而喻了

  • Authorization(授權): 典型場景笛园,?戶請求的token中包含了該令牌允許的路由,服務和資源侍芝。單點登錄其實就是現(xiàn)在?泛使?JWT的?個特性

  • Information Exchange(信息交換): 對于安全的在各?之間傳輸信息??研铆,JSON Web Tokens?疑 是?種很好的?式.因為JWTs可以被簽名

    例如,?公鑰/私鑰對州叠,你可以確定發(fā)送?就是它們所說的 那個?棵红。另外,由于簽名是使?頭和有效負載計算的咧栗,您還可以驗證內(nèi)容沒有被篡改

JWT工作方式是怎樣的逆甜?

image

JWT認證過程基本上整個過程分為兩個階段

  • 第?個階段虱肄,客戶端向服務端獲取token
  • 第?階段,客戶端帶著該token去請求相關的資源

通常?較重要的是交煞,服務端如何根據(jù)指定的規(guī)則進?token的?成浩峡。

在認證的時候,當?戶?他們的憑證成功登錄以后错敢,?個JSON Web Token將會被返回。 此后缕粹,token就是?戶憑證了稚茅,你必須?常??以防?出現(xiàn)安全問題。 ?般??平斩,你保存令牌的時間不應該超過你所需要它的時間亚享。

?論何時?戶想要訪問受保護的路由或者資源的時候,?戶代理(通常是瀏覽器)都應該帶上JWT绘面,典型 的欺税,通常放在Authorization header中,?Bearer schema: Authorization: Bearer <token> 服務器上的受保護的路由將會檢查Authorization header中的JWT是否有效揭璃,如果有效晚凿,則?戶可以訪問 受保護的資源。

如果JWT包含?夠多的必需的數(shù)據(jù)瘦馍,那么就可以減少對某些操作的數(shù)據(jù)庫查詢的需要歼秽,盡管可能并不總是如此。 如果token是在授權頭(Authorization header)中發(fā)送的情组,那么跨源資源共享(CORS)將不會成為問題燥筷,因為它不使?cookie。

來感受一張官方的圖

image

獲取JWT以及訪問APIs以及資源

  • 客戶端向授權接?請求授權

  • 服務端授權后返回?個access token給客戶端

  • 客戶端使?access token訪問受保護的資源

3 基于Token的身份認證和基于服務器的身份認證

1院崇、給予服務器的身份認證肆氓,通常是基于服務器上的session來做用戶認證,使用session會有如下幾個問題

  • Sessions:認證通過后需要將?戶的session數(shù)據(jù)保存在內(nèi)存中底瓣,隨著認證?戶的增加谢揪,內(nèi)存開銷會?
  • 擴展性問題: 由于session存儲在內(nèi)存中,擴展性會受限濒持,雖然后期可以使?redis,memcached來緩存數(shù)據(jù)
  • CORS: 當多個終端訪問同?份數(shù)據(jù)時键耕,可能會遇到禁?請求的問題
  • CSRF: ?戶容易受到CSRF攻擊(Cross Site Request Forgery, 跨站域請求偽造)

2、基于Token的身份認證證是?狀態(tài)的柑营,服務器或者session中不會存儲任何?戶信息.(很好的解決了共享 session的問題)

  • ?戶攜帶?戶名和密碼請求獲取token(接?數(shù)據(jù)中可使?appId,appKey屈雄,或是自己協(xié)商好的某類數(shù)據(jù))

  • 服務端校驗?戶憑證,并返回?戶或客戶端?個Token

  • 客戶端存儲token,并在請求頭中攜帶Token

  • 服務端校驗token并返回相應數(shù)據(jù)

需要注意幾點:

  • 客戶端請求服務器的時候官套,必須將token放到header中
  • 客戶端請求服務器每一次都需要帶上token
  • 服務器需要設置 為接收所有域的請求: Access-Control-Allow-Origin: *

3酒奶、Session和JWT Token的有啥不一樣的蚁孔?

  • 他倆都可以存儲用戶相關的信息
  • session 存儲在服務器, JWT存儲在客戶端
image

4 ?Token有什么好處呢惋嚎?

  • 他是無狀態(tài)的 且 可擴展性好
  • 他相對安全:防?CSRF攻擊杠氢,token過期重新認證

上文有說說,JWT是用于做身份認證的而不是做授權的另伍,那么在這里列舉一下 做認證和做授權分別用在哪里呢鼻百?

  • 例如OAuth2是?種授權框架,是用于授權摆尝,主要用在 使?第三?賬號登錄的情況 (?如使?weibo, qq, github登錄某個app)
  • JWT是?種認證協(xié)議 温艇,?在 前后端分離 , 需要簡單的對后臺API進?保護時使?
  • 無論是授權還是認證,都需要記住使用HTTPS來保護數(shù)據(jù)的安全性

5 實際看看JWT如何做身份驗證

  • jwt做身份驗證堕汞,這里主要講如何根據(jù)header勺爱,payload,signature生成token
  • 客戶端帶著token來服務器做請求讯检,如何校驗琐鲁?

下面實例代碼,主要做了2個接口

用到的技術點:

  • gin
    • 路由分組
    • 中間件的使用
  • gorm
    • 簡單操作mysql數(shù)據(jù)庫人灼,插入围段,查詢
  • jwt
    • 生成token
    • 解析token

登錄接口

訪問url : http://127.0.0.1:9999/v1/login

功能:

  • 用戶登錄
  • 生成jwt,并返回給到客戶端
  • gorm對數(shù)據(jù)庫的操作

認證后Hello接口

訪問url : http://127.0.0.1:9999/v1/auth/hello

功能:

  • 校驗 客戶端請求服務器攜帶token
  • 返回客戶端所請求的數(shù)據(jù)

代碼結構如下圖

image

main.go

package main

import (
   "github.com/gin-gonic/gin"
   "my/controller"
   "my/myauth"
)

func main() {
   //連接數(shù)據(jù)庫
   conErr := controller.InitMySQLCon()
   if conErr != nil {
      panic(conErr)
   }

   //需要使用到gorm投放,因此需要先做一個初始化
   controller.InitModel()
   defer controller.DB.Close()


   route := gin.Default()

   //路由分組
   v1 := route.Group("/v1/")
   {
      //登錄(為了方便蒜撮,將注冊和登錄功能寫在了一起)
      v1.POST("/login", controller.Login)
   }


   v2 := route.Group("/v1/auth/")
   //一個身份驗證的中間件
   v2.Use(myauth.JWTAuth())
   {
      //帶著token請求服務器
      v2.POST("/hello", controller.Hello)
   }

   //監(jiān)聽9999端口
   route.Run(":9999")

}

controller.go

文件中基本的數(shù)據(jù)結構定義為:

image

文件中涉及的處理函數(shù):

image

實際源碼:

package controller

import (
   "errors"
   "fmt"
   jwtgo "github.com/dgrijalva/jwt-go"
   "github.com/gin-gonic/gin"
   "github.com/jinzhu/gorm"
   "log"
   "net/http"
   "time"
   _ "github.com/jinzhu/gorm/dialects/mysql"
)

//登錄請求信息
type ReqInfo struct {
   Name   string `json:"name"`
   Passwd string `json:"passwd"`
}

// 構造用戶表
type MyInfo struct {
   Id        int32  `gorm:"AUTO_INCREMENT"`
   Name      string `json:"name"`
   Passwd    string `json:"passwd"`
   CreatedAt *time.Time
   UpdateTAt *time.Time
}

//Myclaims
// 定義載荷
type Myclaims struct {
   Name string `json:"userName"`
   // StandardClaims結構體實現(xiàn)了Claims接口(Valid()函數(shù))
   jwtgo.StandardClaims
}
//密鑰
type JWT struct {
   SigningKey []byte
}
//hello 接口
func Hello(c *gin.Context) {
   claims, _ := c.MustGet("claims").(*Myclaims)
   if claims != nil {
      c.JSON(http.StatusOK, gin.H{
         "status": 0,
         "msg":    "Hello wrold",
         "data":   claims,
      })
   }
}

var (
   DB               *gorm.DB
   secret                 = "iamsecret"
   TokenExpired     error = errors.New("Token is expired")
   TokenNotValidYet error = errors.New("Token not active yet")
   TokenMalformed   error = errors.New("That's not even a token")
   TokenInvalid     error = errors.New("Couldn't handle this token:")
)
//數(shù)據(jù)庫連接
func InitMySQLCon() (err error) {
   // 可以在api包里設置成init函數(shù)
   connStr := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", "root", "123456", "127.0.0.1", 3306, "mygorm")
   fmt.Println(connStr)
   DB, err = gorm.Open("mysql", connStr)

   if err != nil {
      return err
   }

   return DB.DB().Ping()
}
//初始化gorm對象映射
func InitModel() {
   DB.AutoMigrate(&MyInfo{})
}
func NewJWT() *JWT {
   return &JWT{
      []byte(secret),
   }
}
// 登陸結果
type LoginResult struct {
   Token string `json:"token"`
   Name string `json:"name"`
}
// 創(chuàng)建Token(基于用戶的基本信息claims)
// 使用HS256算法進行token生成
// 使用用戶基本信息claims以及簽名key(signkey)生成token
func (j *JWT) CreateToken(claims Myclaims) (string, error) {
   // 返回一個token的結構體指針
   token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS256, claims)
   return token.SignedString(j.SigningKey)
}
//生成token
func generateToken(c *gin.Context, info ReqInfo) {
   // 構造SignKey: 簽名和解簽名需要使用一個值
   j := NewJWT()

   // 構造用戶claims信息(負荷)
   claims := Myclaims{
      info.Name,
      jwtgo.StandardClaims{
         NotBefore: int64(time.Now().Unix() - 1000), // 簽名生效時間
         ExpiresAt: int64(time.Now().Unix() + 3600), // 簽名過期時間
         Issuer:    "pangsir",                       // 簽名頒發(fā)者
      },
   }

   // 根據(jù)claims生成token對象
   token, err := j.CreateToken(claims)
   if err != nil {
      c.JSON(http.StatusOK, gin.H{
         "status": -1,
         "msg":    err.Error(),
         "data":   nil,
      })
   }

   log.Println(token)
   // 返回用戶相關數(shù)據(jù)
   data := LoginResult{
      Name:  info.Name,
      Token: token,
   }

   c.JSON(http.StatusOK, gin.H{
      "status": 0,
      "msg":    "登陸成功",
      "data":   data,
   })

   return
}
//解析token
func (j *JWT) ParserToken(tokenstr string) (*Myclaims, error) {
   // 輸入token
   // 輸出自定義函數(shù)來解析token字符串為jwt的Token結構體指針
   // Keyfunc是匿名函數(shù)類型: type Keyfunc func(*Token) (interface{}, error)
   // func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {}
   token, err := jwtgo.ParseWithClaims(tokenstr, &Myclaims{}, func(token *jwtgo.Token) (interface{}, error) {
      return j.SigningKey, nil
   })

   fmt.Println(token, err)
   if err != nil {
      // jwt.ValidationError 是一個無效token的錯誤結構
      if ve, ok := err.(*jwtgo.ValidationError); ok {
         // ValidationErrorMalformed是一個uint常量,表示token不可用
         if ve.Errors&jwtgo.ValidationErrorMalformed != 0 {
            return nil, TokenMalformed
            // ValidationErrorExpired表示Token過期
         } else if ve.Errors&jwtgo.ValidationErrorExpired != 0 {
            return nil, TokenExpired
            // ValidationErrorNotValidYet表示無效token
         } else if ve.Errors&jwtgo.ValidationErrorNotValidYet != 0 {
            return nil, TokenNotValidYet
         } else {
            return nil, TokenInvalid
         }

      }
   }

   // 將token中的claims信息解析出來和用戶原始數(shù)據(jù)進行校驗
   // 做以下類型斷言跪呈,將token.Claims轉換成具體用戶自定義的Claims結構體
   if claims, ok := token.Claims.(*Myclaims); ok && token.Valid {
      return claims, nil
   }

   return nil, errors.New("token NotValid")
}
//登錄
func Login(c *gin.Context) {
   var reqinfo ReqInfo
   var userInfo MyInfo

   err := c.BindJSON(&reqinfo)

   if err == nil {
      fmt.Println(reqinfo)

      if reqinfo.Name == "" || reqinfo.Passwd == ""{
         c.JSON(http.StatusOK, gin.H{
            "status": -1,
            "msg":    "賬號密碼不能為空",
            "data":   nil,
         })
         c.Abort()
         return
      }
      //校驗數(shù)據(jù)庫中是否有該用戶
      err := DB.Where("name = ?", reqinfo.Name).Find(&userInfo)
      if err != nil {
         fmt.Println("數(shù)據(jù)庫中沒有該用戶 段磨,可以進行添加用戶數(shù)據(jù)")
         //添加用戶到數(shù)據(jù)庫中
         info := MyInfo{
            Name:   reqinfo.Name,
            Passwd: reqinfo.Passwd,
         }
         dberr := DB.Model(&MyInfo{}).Create(&info).Error
         if dberr != nil {
            c.JSON(http.StatusOK, gin.H{
               "status": -1,
               "msg":    "登錄失敗,數(shù)據(jù)庫操作錯誤",
               "data":   nil,
            })
            c.Abort()
            return
         }
      }else{
         if userInfo.Name != reqinfo.Name || userInfo.Passwd != reqinfo.Passwd{
            c.JSON(http.StatusOK, gin.H{
               "status": -1,
               "msg":    "賬號密碼錯誤",
               "data":   nil,
            })
            c.Abort()
            return
         }
      }
      //創(chuàng)建token
      generateToken(c, reqinfo)
   } else {
      c.JSON(http.StatusOK, gin.H{
         "status": -1,
         "msg":    "登錄失敗耗绿,數(shù)據(jù)請求錯誤",
         "data":   nil,
      })
   }
}

myauth.go

package myauth

import (
   "fmt"
   "github.com/gin-gonic/gin"
   "my/controller"
   "net/http"
)

//身份認證
func JWTAuth() gin.HandlerFunc {
   return func(c *gin.Context) {
      //拿到token
      token := c.Request.Header.Get("token")
      if token == "" {
         c.JSON(http.StatusOK, gin.H{
            "status": -1,
            "msg":    "token為空苹支,請攜帶token",
            "data":   nil,
         })
         c.Abort()
         return
      }

      fmt.Println("token = ", token)

      //解析出實際的載荷
      j := controller.NewJWT()

      claims, err := j.ParserToken(token)
      if err != nil {
         // token過期
         if err == controller.TokenExpired {
            c.JSON(http.StatusOK, gin.H{
               "status": -1,
               "msg":    "token授權已過期,請重新申請授權",
               "data":   nil,
            })
            c.Abort()
            return
         }
         // 其他錯誤
         c.JSON(http.StatusOK, gin.H{
            "status": -1,
            "msg":    err.Error(),
            "data":   nil,
         })
         c.Abort()
         return
      }

      // 解析到具體的claims相關信息
      c.Set("claims", claims)
   }
}

myauth.go

package myauth

import (
   "fmt"
   "github.com/gin-gonic/gin"
   "my/controller"
   "net/http"
)

//身份認證
func JWTAuth() gin.HandlerFunc {
   return func(c *gin.Context) {
      //拿到token
      token := c.Request.Header.Get("token")
      if token == "" {
         c.JSON(http.StatusOK, gin.H{
            "status": -1,
            "msg":    "token為空误阻,請攜帶token",
            "data":   nil,
         })
         c.Abort()
         return
      }

      fmt.Println("token = ", token)

      //解析出實際的載荷
      j := controller.NewJWT()

      claims, err := j.ParserToken(token)
      if err != nil {
         // token過期
         if err == controller.TokenExpired {
            c.JSON(http.StatusOK, gin.H{
               "status": -1,
               "msg":    "token授權已過期债蜜,請重新申請授權",
               "data":   nil,
            })
            c.Abort()
            return
         }
         // 其他錯誤
         c.JSON(http.StatusOK, gin.H{
            "status": -1,
            "msg":    err.Error(),
            "data":   nil,
         })
         c.Abort()
         return
      }

      // 解析到具體的claims相關信息
      c.Set("claims", claims)
   }
}

6 jwt是如何將header,paylaod,signature組裝在一起的?

1> 我們從創(chuàng)建token的函數(shù)開始看起

image

CreateToken用JWT對象綁定究反,對象中包含密鑰寻定,函數(shù)的參數(shù)是載荷

2> NewWithClaims 函數(shù)參數(shù)是加密算法,載荷

NewWithClaims的具體作用是是初始化一個Token對象

image
image

3> SignedString函數(shù)精耐,參數(shù)為密鑰

主要是得到一個完整的token

SigningString 將header 與 載荷 處理后拼接在一起

Sign 將密鑰計算一個hash值狼速,與header,載荷拼接在一起卦停,進而制作成token

此處的Sign 方法具體是調(diào)用哪一個實現(xiàn)向胡,請繼續(xù)往下看

image

4> SigningString

將header通過json序列化之后使用base64加密

同樣的也將載荷通過json序列化之后使用base64加密

將這倆加密后的字符串拼接在一起

image
image

5> 回到創(chuàng)建token函數(shù)的位置

func (j *JWT) CreateToken(claims Myclaims) (string, error) {
   // 返回一個token的結構體指針
   token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS256, claims)
   return token.SignedString(j.SigningKey)
}

SigningMethodHS256 對應這一個結構SigningMethodHMAC恼蓬,如下

image

看到這里,便解開了上述第4點 Sign方法具體在哪里實現(xiàn)的問題

image

7> 效果查看

登錄&注冊接口

image

數(shù)據(jù)庫展示(若對編碼中的gorm有疑問僵芹,可以看小魔童哪吒的上一期gorm的整理)

image

Hello接口

image

以上為本期全部內(nèi)容处硬,如有疑問可以在評論區(qū)或后臺提出你的疑問,我們一起交流拇派,一起成長荷辕。

好家伙要是文章對你還有點作用的話,請幫忙點個關注件豌,分享到你的朋友圈桐腌,分享技術,分享快樂

技術是開放的苟径,我們的心態(tài),更應是開放的躬审。擁抱變化棘街,向陽而生,努力向前行承边。

作者:小魔童哪吒

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末遭殉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子博助,更是在濱河造成了極大的恐慌险污,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件富岳,死亡現(xiàn)場離奇詭異蛔糯,居然都是意外死亡,警方通過查閱死者的電腦和手機窖式,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門蚁飒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萝喘,你說我怎么就攤上這事淮逻。” “怎么了阁簸?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵爬早,是天一觀的道長。 經(jīng)常有香客問我启妹,道長筛严,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任饶米,我火速辦了婚禮脑漫,結果婚禮上髓抑,老公的妹妹穿的比我還像新娘。我一直安慰自己优幸,他們只是感情好吨拍,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著网杆,像睡著了一般羹饰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碳却,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天队秩,我揣著相機與錄音,去河邊找鬼昼浦。 笑死馍资,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的关噪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼建钥,長吁一口氣:“原來是場噩夢啊……” “哼虐沥!你這毒婦竟也來了熊经?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤镐依,失蹤者是張志新(化名)和其女友劉穎天试,沒想到半個月后馋吗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡秋秤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绍哎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鞋真。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖海诲,靈堂內(nèi)的尸體忽然破棺而出繁莹,到底是詐尸還是另有隱情特幔,我是刑警寧澤蚯斯,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站拍嵌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏横辆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一困肩、第九天 我趴在偏房一處隱蔽的房頂上張望炫惩。 院中可真熱鬧阿浓,春花似錦、人聲如沸筋蓖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓮下。三九已至钝域,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間例证,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工胀葱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人庆锦。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓晌该,卻偏偏與公主長得像,于是被迫代替她去往敵國和親燕耿。 傳聞我的和親對象是個殘疾皇子姜胖,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容