網(wǎng)站首頁 編程語言 正文
什么是JWT?
JWT全稱JSON Web Token是一種跨域認(rèn)證解決方案,屬于一個(gè)開放的標(biāo)準(zhǔn),它規(guī)定了一種Token實(shí)現(xiàn)方式,目前多用于前后端分離項(xiàng)目和OAuth3.0業(yè)務(wù)場(chǎng)景下。
為什么需要JWT?
在之前的一些web項(xiàng)目中,我們通常使用的是Cookie-Session
模式實(shí)現(xiàn)用戶認(rèn)證。相關(guān)流程大致如下:
- 用戶在瀏覽器端填寫用戶名和密碼,并發(fā)送給服務(wù)端
- 服務(wù)端對(duì)用戶名和密碼校驗(yàn)通過后會(huì)生成一份保存當(dāng)前用戶相關(guān)信息的session數(shù)據(jù)和一個(gè)與之對(duì)應(yīng)的標(biāo)識(shí)(通常稱為session_id)
- 服務(wù)端返回響應(yīng)時(shí)將上一步的session_id寫入用戶瀏覽器的Cookie
- 后續(xù)用戶來自該瀏覽器的每次請(qǐng)求都會(huì)自動(dòng)攜帶包含session_id的Cookie
- 服務(wù)端通過請(qǐng)求中的session_id就能找到之前保存的該用戶那份session數(shù)據(jù),從而獲取該用戶的相關(guān)信息。
這種方案依賴于客戶端(瀏覽器)保存Cookie,并且需要在服務(wù)端存儲(chǔ)用戶的session數(shù)據(jù)。
在移動(dòng)互聯(lián)網(wǎng)時(shí)代,我們的用戶可能使用瀏覽器也可能使用APP來訪問我們的服務(wù),我們的web應(yīng)用可能是前后端分開部署在不同的端口,有時(shí)候我們還需要支持第三方登錄,這下Cookie-Session
的模式就有些力不從心了。
JWT就是一種基于Token的輕量級(jí)認(rèn)證模式,服務(wù)端認(rèn)證通過后,會(huì)生成一個(gè)JSON對(duì)象,經(jīng)過簽名后得到一個(gè)Token(令牌)再發(fā)回給用戶,用戶后續(xù)請(qǐng)求只需要帶上這個(gè)Token,服務(wù)端解密之后就能獲取該用戶的相關(guān)信息了。
想要連接JWT的原理,推薦大家閱讀:JWT入門教程
生成JWT和解析JWT
我們?cè)谶@里直接使用jwt-go
這個(gè)庫來實(shí)現(xiàn)我們生成JWT和解析JWT的功能。
定義需求
我們需要定制自己的需求來決定JWT中保存哪些數(shù)據(jù),比如我們規(guī)定在JWT中要存儲(chǔ)username
信息,那么我們就定義一個(gè)MyClaims
結(jié)構(gòu)體如下:
import ( "github.com/dgrijalva/jwt-go" ) // MyClaims 自定義聲明結(jié)構(gòu)體并內(nèi)嵌jwt.StandardClaims // jwt包自帶的jwt.StandardClaims只包含了官方字段 // 我們這里需要額外記錄一個(gè)username字段,所以要自定義結(jié)構(gòu)體 // 如果想要保存更多信息,都可以添加到這個(gè)結(jié)構(gòu)體中 type MyClaims struct { Username string `json:"username"` jwt.StandardClaims }
然后我們定義JWT的過期時(shí)間,這里以2小時(shí)為例:
const TokenExpireDuration = time.Hour * 2
接下來還需要定義Secret:
var MySecret = []byte("夏天夏天悄悄過去")
生成JWT
// GenToken 生成JWT func GenToken(username string) (string, error) { // 創(chuàng)建一個(gè)我們自己的聲明 c := MyClaims{ "username", // 自定義字段 jwt.StandardClaims{ ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 過期時(shí)間 Issuer: "my-project", // 簽發(fā)人 }, } // 使用指定的簽名方法創(chuàng)建簽名對(duì)象 token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) // 使用指定的secret簽名并獲得完整的編碼后的字符串token return token.SignedString(MySecret) }
解析JWT
// ParseToken 解析JWT func ParseToken(tokenString string) (*MyClaims, error) { // 解析token token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) { return MySecret, nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(*MyClaims); ok && token.Valid { // 校驗(yàn)token return claims, nil } return nil, errors.New("invalid token") }
在gin框架中使用JWT
首先我們注冊(cè)一條路由/auth
,對(duì)外提供獲取Token的渠道:
r.POST("/auth", authHandler)
我們的authHandler
定義如下:
func authHandler(c *gin.Context) { // 用戶發(fā)送用戶名和密碼過來 var user UserInfo err := c.ShouldBind(&user) if err != nil { c.JSON(http.StatusOK, gin.H{ "code": 2001, "msg": "無效的參數(shù)", }) return } // 校驗(yàn)用戶名和密碼是否正確 if user.Username == "q1mi" && user.Password == "q1mi123" { // 生成Token tokenString, _ := GenToken(user.Username) c.JSON(http.StatusOK, gin.H{ "code": 2000, "msg": "success", "data": gin.H{"token": tokenString}, }) return } c.JSON(http.StatusOK, gin.H{ "code": 2002, "msg": "鑒權(quán)失敗", }) return }
用戶通過上面的接口獲取Token之后,后續(xù)就會(huì)攜帶著Token再來請(qǐng)求我們的其他接口,這個(gè)時(shí)候就需要對(duì)這些請(qǐng)求的Token進(jìn)行校驗(yàn)操作了,很顯然我們應(yīng)該實(shí)現(xiàn)一個(gè)檢驗(yàn)Token的中間件,具體實(shí)現(xiàn)如下:
// JWTAuthMiddleware 基于JWT的認(rèn)證中間件 func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // 客戶端攜帶Token有三種方式 1.放在請(qǐng)求頭 2.放在請(qǐng)求體 3.放在URI // 這里假設(shè)Token放在Header的Authorization中,并使用Bearer開頭 // 這里的具體實(shí)現(xiàn)方式要依據(jù)你的實(shí)際業(yè)務(wù)情況決定 authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { c.JSON(http.StatusOK, gin.H{ "code": 2003, "msg": "請(qǐng)求頭中auth為空", }) c.Abort() return } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { c.JSON(http.StatusOK, gin.H{ "code": 2004, "msg": "請(qǐng)求頭中auth格式有誤", }) c.Abort() return } // parts[1]是獲取到的tokenString,我們使用之前定義好的解析JWT的函數(shù)來解析它 mc, err := ParseToken(parts[1]) if err != nil { c.JSON(http.StatusOK, gin.H{ "code": 2005, "msg": "無效的Token", }) c.Abort() return } // 將當(dāng)前請(qǐng)求的username信息保存到請(qǐng)求的上下文c上 c.Set("username", mc.Username) c.Next() // 后續(xù)的處理函數(shù)可以用過c.Get("username")來獲取當(dāng)前請(qǐng)求的用戶信息 } }
注冊(cè)一個(gè)/home
路由,發(fā)個(gè)請(qǐng)求驗(yàn)證一下吧。
r.GET("/home", JWTAuthMiddleware(), homeHandler) func homeHandler(c *gin.Context) { username := c.MustGet("username").(string) c.JSON(http.StatusOK, gin.H{ "code": 2000, "msg": "success", "data": gin.H{"username": username}, }) }
如果不想自己實(shí)現(xiàn)上述功能,你也可以使用Github上別人封裝好的包。
原文鏈接:https://www.cnblogs.com/guyouyin123/p/14576927.html
相關(guān)推薦
- 2021-11-22 C++?STL中五個(gè)常用算法使用教程及實(shí)例講解_C 語言
- 2022-12-06 Python之列表的append()方法最容易踩的坑_python
- 2022-05-07 redis實(shí)現(xiàn)分布式session的解決方案_Redis
- 2022-07-08 C語言算法學(xué)習(xí)之雙向鏈表詳解_C 語言
- 2022-06-12 C#泛型接口的協(xié)變和逆變_C#教程
- 2024-03-18 為什么SpringBoot的jar可以直接運(yùn)行?
- 2021-12-13 C語言魔方陣的三種實(shí)現(xiàn)方法_C 語言
- 2022-12-19 Android硬件解碼組件MediaCodec使用教程_Android
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支