Go語言實現雙Token登錄的思路與實現

Go語言實現雙Token登錄的思路與實現

引言

在現代Web應用中,身份認證是保障系統安全的重要環節。傳統的單Token認證方式存在一些安全隱患,如Token泄露可能導致長期風險。雙Token機制(Access Token + Refresh Token)提供了更好的安全性和用戶體驗。本文將介紹如何使用Go語言實現雙Token登錄系統。

雙Token機制概述

雙Token機制包含兩種令牌:

  1. Access Token:短期有效的令牌,用于訪問受保護資源
  2. Refresh Token:長期有效的令牌,用于獲取新的Access Token

這種機制的優勢在于:

  • Access Token有效期短,即使泄露影響有限
  • Refresh Token不直接用于資源訪問,降低了泄露風險
  • 無需頻繁重新登錄,保持用戶體驗

實現思路

1. 數據結構設計

首先定義Token相關的數據結構:

type TokenDetails struct {AccessToken  stringRefreshToken stringAccessUuid   stringRefreshUuid  stringAtExpires    int64RtExpires    int64
}type AccessDetails struct {AccessUuid stringUserId     uint64
}

2. Token生成與存儲

使用JWT(JSON Web Token)生成Token,并存儲在Redis中:

func CreateToken(userid uint64) (*TokenDetails, error) {td := &TokenDetails{}td.AtExpires = time.Now().Add(time.Minute * 15).Unix()td.AccessUuid = uuid.New().String()td.RtExpires = time.Now().Add(time.Hour * 24 * 7).Unix()td.RefreshUuid = uuid.New().String()// 創建Access TokenatClaims := jwt.MapClaims{}atClaims["authorized"] = trueatClaims["access_uuid"] = td.AccessUuidatClaims["user_id"] = useridatClaims["exp"] = td.AtExpiresat := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims)td.AccessToken, _ = at.SignedString([]byte(os.Getenv("ACCESS_SECRET")))// 創建Refresh TokenrtClaims := jwt.MapClaims{}rtClaims["refresh_uuid"] = td.RefreshUuidrtClaims["user_id"] = useridrtClaims["exp"] = td.RtExpiresrt := jwt.NewWithClaims(jwt.SigningMethodHS256, rtClaims)td.RefreshToken, _ = rt.SignedString([]byte(os.Getenv("REFRESH_SECRET")))return td, nil
}func CreateAuth(userid uint64, td *TokenDetails) error {at := time.Unix(td.AtExpires, 0)rt := time.Unix(td.RtExpires, 0)now := time.Now()// 存儲Access TokenerrAccess := client.Set(td.AccessUuid, strconv.Itoa(int(userid)), at.Sub(now)).Err()if errAccess != nil {return errAccess}// 存儲Refresh TokenerrRefresh := client.Set(td.RefreshUuid, strconv.Itoa(int(userid)), rt.Sub(now)).Err()if errRefresh != nil {return errRefresh}return nil
}

3. 登錄接口實現

func Login(c *gin.Context) {var user Userif err := c.ShouldBindJSON(&user); err != nil {c.JSON(http.StatusUnprocessableEntity, "Invalid json provided")return}// 驗證用戶憑據// ...// 生成Tokentd, err := CreateToken(user.ID)if err != nil {c.JSON(http.StatusUnprocessableEntity, err.Error())return}// 存儲TokensaveErr := CreateAuth(user.ID, td)if saveErr != nil {c.JSON(http.StatusUnprocessableEntity, saveErr.Error())return}tokens := map[string]string{"access_token":  td.AccessToken,"refresh_token": td.RefreshToken,}c.JSON(http.StatusOK, tokens)
}

4. Token刷新機制

func Refresh(c *gin.Context) {mapToken := map[string]string{}if err := c.ShouldBindJSON(&mapToken); err != nil {c.JSON(http.StatusUnprocessableEntity, err.Error())return}refreshToken := mapToken["refresh_token"]// 驗證Refresh Tokentoken, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) {if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])}return []byte(os.Getenv("REFRESH_SECRET")), nil})if err != nil {c.JSON(http.StatusUnauthorized, "Refresh token expired")return}// 檢查Token是否有效if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid {c.JSON(http.StatusUnauthorized, err)return}// 提取claimsclaims, ok := token.Claims.(jwt.MapClaims)if ok && token.Valid {refreshUuid, ok := claims["refresh_uuid"].(string)if !ok {c.JSON(http.StatusUnprocessableEntity, err)return}userId, err := strconv.ParseUint(fmt.Sprintf("%.f", claims["user_id"]), 10, 64)if err != nil {c.JSON(http.StatusUnprocessableEntity, "Error occurred")return}// 刪除舊的Refresh Tokendeleted, delErr := DeleteAuth(refreshUuid)if delErr != nil || deleted == 0 {c.JSON(http.StatusUnauthorized, "unauthorized")return}// 創建新的Token對ts, createErr := CreateToken(userId)if createErr != nil {c.JSON(http.StatusForbidden, createErr.Error())return}// 保存新的TokensaveErr := CreateAuth(userId, ts)if saveErr != nil {c.JSON(http.StatusForbidden, saveErr.Error())return}tokens := map[string]string{"access_token":  ts.AccessToken,"refresh_token": ts.RefreshToken,}c.JSON(http.StatusCreated, tokens)} else {c.JSON(http.StatusUnauthorized, "refresh expired")}
}

5. 中間件實現Token驗證

func TokenAuthMiddleware() gin.HandlerFunc {return func(c *gin.Context) {err := TokenValid(c.Request)if err != nil {c.JSON(http.StatusUnauthorized, err.Error())c.Abort()return}c.Next()}
}func TokenValid(r *http.Request) error {token, err := VerifyToken(r)if err != nil {return err}if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid {return err}return nil
}func VerifyToken(r *http.Request) (*jwt.Token, error) {tokenString := ExtractToken(r)token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])}return []byte(os.Getenv("ACCESS_SECRET")), nil})if err != nil {return nil, err}return token, nil
}func ExtractToken(r *http.Request) string {bearToken := r.Header.Get("Authorization")strArr := strings.Split(bearToken, " ")if len(strArr) == 2 {return strArr[1]}return ""
}

完整流程

  1. 用戶登錄:提供用戶名密碼,服務端驗證后返回Access Token和Refresh Token
  2. 訪問受保護資源:客戶端在請求頭中攜帶Access Token
  3. Access Token過期:服務端返回401錯誤
  4. 刷新Token:客戶端使用Refresh Token請求新的Token對
  5. 繼續訪問:使用新的Access Token訪問資源

總結

通過Go語言實現雙Token認證機制,我們能夠構建更安全的身份認證系統。這種機制在保證安全性的同時,也提供了良好的用戶體驗。實際應用中,可以根據業務需求調整Token的有效期和實現細節。

希望這篇文章對你理解和使用雙Token認證有所幫助!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/87806.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/87806.shtml
英文地址,請注明出處:http://en.pswp.cn/web/87806.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

映射阿里云OSS(對象存儲服務)

參考:使用阿里云進行OSS對象存儲(超詳細) 一文掌握SpringBoot注解之Component 知識文集(1) ConfigurationProperties注解原理與實戰 1.配置屬性類 AliOssProperties package com.sky.properties;import lombok.Data; import org.springframe…

Java操作word實戰

文章目錄簡介段落頁頭與頁腳頁碼表格圖片批注文本框目錄圖表簡介 Word編程最重要的類是org.apache.poi.xwpf.usermodel.XWPFDocument。涉及的東西十分復雜。而且Apache poi操作word的技術非常不成熟。代碼中本身有很多bug。 ??Maven的依賴為 <dependency><groupId&…

【Flask】flask中get方法和post方法區別

對于post和get在我以前的認知下一直認為是&#xff1a; 前端發送給后端就稱為post 前端需要從后端返回就用get 但是在開發過程中發現了不僅僅如此 區別 GET 意圖&#xff1a;獲取&#xff08;GET&#xff09; 信息。你只是想讀取服務器上已經存在的資源&#xff0c;你不打算改變…

Linux sudo升級

應對 Linux sudo 本地提權漏洞&#xff1a;離線升級 Sudo 到安全版本 一、引言 在 Linux 系統中&#xff0c;sudo&#xff08;superuser do&#xff09;是一個非常重要的工具&#xff0c;它允許授權用戶以超級用戶&#xff08;root&#xff09;的權限執行命令。然而&#xff0c…

ubuntu 6.8.0 安裝xenomai3.3

通過以下步驟來獲取和準備 Linux 內核 6.8.0 的源碼&#xff0c;并應用 Xenomai 補丁&#xff1a; 1. 下載 Linux 內核 6.8.0 源碼 你可以從 The Linux Kernel Archives 下載 Linux 內核 6.8.0 的源碼。以下是具體步驟&#xff1a; 訪問內核官方網站&#xff1a; 打開 The Li…

drawRect 觸發時機

在 iOS 開發中&#xff0c;UIView 的 drawRect: 方法&#xff08;或其底層 CALayer 的繪制&#xff09;的觸發時機是由系統控制的&#xff0c;開發者不能直接調用這些方法。以下是觸發視圖繪制的完整機制&#xff1a;一、核心觸發時機 1. 視圖首次顯示 當視圖被添加到視圖層級時…

1.1_4 計算機網絡的分類

在這個視頻中我們會探討計算機網絡的分類&#xff0c;從不同的角度可以對計算機網絡進行不同的分類&#xff0c;我們會從分布范圍、傳輸技術、拓撲結構、使用者和傳輸介質這樣的幾個維度進行討論&#xff0c;在這門課當中需要注意的是標紅色的幾個分類&#xff0c;其他的類別簡…

03每日簡報20250705

每日簡報 新聞簡報&#xff1a;AI行業信任危機浮現 標題&#xff1a;知名科技作者Alberto Romero發文《我對AI行業正在失去所有信任》 來源&#xff1a;The Algorithmic Bridge&#xff08;算法之橋&#xff09; 核心內容&#xff1a; 作者立場&#xff1a;長期支持AI技術…

Python 多版本環境治理理念驅動的系統架構設計:三維治理、四級隔離、五項自治 原則

Python 多版本與開發環境治理架構設計-CSDN博客 Python 多版本治理理念&#xff08;Windows 平臺 零基礎友好&#xff09;-CSDN博客 Python 多版本開發環境治理&#xff1a;理論架構與實踐-CSDN博客 【終極實戰】Conda/Poetry/Virtualenv/Pipenv/Hatch 多工具協同 AnacondaP…

C++ 第四階段 文件IO - 第一節:ifstream/ofstream操作

目錄 一、文件 IO 的基本概念 二、文件流的基本操作 1. 打開文件 2. 關閉文件 3. 檢查文件是否成功打開 三、文本文件的讀寫操作 1. 寫入文本文件&#xff08;ofstream&#xff09; 2. 讀取文本文件&#xff08;ifstream&#xff09; 四、二進制文件的讀寫操作 1. 寫…

容聲W60以光水離子科技實現食材“主動養鮮”

炎炎夏日&#xff0c;孩子沉迷電視手機屏幕&#xff0c;視力堪憂&#xff1f;高價買回的“超級食物”羽衣甘藍、車厘子&#xff0c;幾天就蔫了&#xff1f;切開的西瓜放進冰箱&#xff0c;卻怕沾染細菌&#xff1f;7月5日&#xff0c;容聲冰箱“WILL養鮮 高能一夏”新品發布會給…

力扣面試150(13/150)

7.3 380. O(1) 時間插入、刪除和獲取隨機元素 實現RandomizedSet 類&#xff1a; RandomizedSet() 初始化 RandomizedSet 對象bool insert(int val) 當元素 val 不存在時&#xff0c;向集合中插入該項&#xff0c;并返回 true &#xff1b;否則&#xff0c;返回 false 。bool…

需要scl來指定編譯器的clangd+cmake在vscode/cursor開發環境下的配置

最近cursor更新了插件商店&#xff0c;只能使用默認它魔改的c/c插件&#xff08;基于clangd的&#xff09;&#xff0c;手頭剛好在折騰一個cmake工程&#xff0c;試試水嘗試直接配置在cursor上可以編譯運行。 主要是本地環境使用scl來管理gcc/g&#xff0c;所以在配置過程中需要…

docker離線/在線環境下安裝elasticsearch

如果想離線安裝docker、redis、gninx、mysql可參照下面這個。 離線環境下&#xff0c;docker安裝redis、ngnix、mysql 獲取離線包 方式1 找一個能上網的環境&#xff0c;下載elasticsearch的鏡像&#xff0c;然后將這個鏡像導出 docker pull docker.elastic.co/elasticsear…

響應式編程入門教程第一節:揭秘 UniRx 核心 - ReactiveProperty - 讓你的數據動起來!

響應式編程入門教程第一節&#xff1a;揭秘 UniRx 核心 - ReactiveProperty - 讓你的數據動起來&#xff01;-CSDN博客 響應式編程入門教程第二節&#xff1a;構建 ObservableProperty&#xff1c;T&#xff1e; — 封裝 ReactiveProperty 的高級用法-CSDN博客 今天我們來聊聊…

單片機:STM32F103的開發環境搭建

本文將詳細介紹如何搭建STM32F103的開發環境。STM32F103是STMicroelectronics推出的一款基于ARM Cortex-M3內核的32位微控制器&#xff08;MCU&#xff09;&#xff0c;廣泛應用于嵌入式開發。以下是搭建開發環境的詳細步驟&#xff0c;涵蓋硬件準備、軟件安裝、工具鏈配置及簡…

eNSP中實現vlan間路由通信(路由器)

eNSP中實現vlan間路由通信&#xff08;路由器&#xff09; 拓撲圖PC配置 pc1&#xff1a;192.168.10.1255.255.255.0192.168.10.254pc2&#xff1a;192.168.20.1255.255.255.0192.168.20.254pc3&#xff1a; 192.168.10.2255.255.255.0192.168.10.254pc4:192.168.20.2255.255.2…

spring6合集——spring概述以及OCP、DIP、IOC原則

spring6合集——Spring6核心知識點總結啟示錄一、SOLID原則1. 單一職責原則&#xff08;SRP&#xff09;2. 開閉原則&#xff08;OCP&#xff09;3. 里氏替換原則&#xff08;LSP&#xff09;4. 接口隔離原則&#xff08;ISP&#xff09;5. 依賴倒置原則&#xff08;DIP&#x…

Stata如何做機器學習?——SHAP解釋框架下的足球運動員價值驅動因素識別:基于H2O集成學習模型

SHAP解釋框架下的足球運動員價值驅動因素識別——基于H2O集成學習模型? 歡迎關注 「阿水實證通」&#xff0c;前沿方法時刻看&#xff01;&#x1f31f;&#x1f31f;&#x1f31f; 文章目錄 SHAP解釋框架下的足球運動員價值驅動因素識別——基于H2O集成學習模型?聚焦&…

基于Android的益智游戲學習系統

博主介紹&#xff1a;java高級開發&#xff0c;從事互聯網行業多年&#xff0c;熟悉各種主流語言&#xff0c;精通java、python、php、爬蟲、web開發&#xff0c;已經做了多年的畢業設計程序開發&#xff0c;開發過上千套畢業設計程序&#xff0c;沒有什么華麗的語言&#xff0…