Go?+?Gin實(shí)現(xiàn)雙Token管理員登錄的示例代碼
一、為什么要做「雙 Token」
傳統(tǒng)單 Token(JWT)架構(gòu)下,“續(xù)簽”與“強(qiáng)制失效” 是一對(duì)矛盾:
| 單 Token 痛點(diǎn) | 雙 Token 解法 |
|---|---|
| Token 過(guò)期后需重新登錄,體驗(yàn)差 | Access-Token 失效后,用 Refresh-Token 無(wú)感刷新 |
| Token 一旦泄露,在有效期內(nèi)無(wú)法撤銷(xiāo) | Refresh-Token 存 Redis,可一鍵踢人 |
| 續(xù)簽邏輯侵入業(yè)務(wù)代碼 | 續(xù)簽、校驗(yàn)、踢人全部封裝在 Auth 中間件 |
本文將用 Gin + GORM + Redis 帶你落地一套生產(chǎn)可用的「管理員雙 Token」登錄體系。
二、整體架構(gòu)
┌────────────┐ ┌──────────────┐ ┌──────────┐
│ Web │──────?│ Handler │──────?│ Logic │
└────────────┘ └──────────────┘ └──────────┘
▲ ▲ │
│ │ ▼
│ ┌──────────────┐ ┌──────────┐
│ │ Response │ │ Repo │
└────────────┘ Helper │ └──────────┘
│
Redis / MySQL
- Handler 負(fù)責(zé)參數(shù)校驗(yàn)、鑒權(quán)前置檢查
- Logic 處理核心業(yè)務(wù):登錄、刷新、退出、獲取用戶(hù)信息
- Repo 封裝數(shù)據(jù)訪問(wèn):MySQL 查管理員、Redis 存 Refresh-Token
- Response 統(tǒng)一封裝返回格式,避免樣板代碼
三、核心代碼走讀
3.1 路由層(Handler)
// NewAdminLoginHandler 管理員登錄
func NewAdminLoginHandler() gin.HandlerFunc {
return func(c *gin.Context) {
var req requests.AdminLoginReq
if err := c.ShouldBindJSON(&req); err != nil {
response.Fail(c, "參數(shù)不合法")
return
}
repo := repositories.NewAdminLoginRepository(globals.DB, globals.RDB)
logic := logics.NewAdminLoginLogic(repo)
resp, err := logic.Login(c.Request.Context(), &req)
if err != nil {
response.Fail(c, fmt.Sprintf("登錄失敗: %v", err))
return
}
response.OK(c, "登錄成功", resp)
}
}
- 只做「綁定參數(shù) + 調(diào)用邏輯 + 包裝返回」
- 不摻雜任何業(yè)務(wù)判斷,保持 單一職責(zé)
3.2 業(yè)務(wù)層(Logic)
3.2.1 登錄
func (l *AdminLoginLogic) Login(ctx context.Context, req *requests.AdminLoginReq) (*responses.AdminLoginResp, error) {
admin, err := l.findAdminByAccount(ctx, req.Account)
if err != nil || admin == nil {
return nil, errors.New("賬號(hào)不存在")
}
if admin.Status != 1 {
return nil, errors.New("賬號(hào)已被禁用")
}
if !usersignutil.CheckPasswordHash(req.Password, admin.Password) {
return nil, errors.New("密碼錯(cuò)誤")
}
// 更新最后登錄時(shí)間
_ = l.adminRepo.UpdateLastLoginTime(ctx, admin.ID)
// 生成雙 Token
return l.generateTokensAndBuildResponse(ctx, admin)
}
3.2.2 生成雙 Token
func (l *AdminLoginLogic) generateTokensAndBuildResponse(
ctx context.Context,
admin *models.Admin,
) (*responses.AdminLoginResp, error) {
accessToken, _ := usersignutil.GenerateAccessToken(admin.ID)
refreshToken, _ := usersignutil.GenerateRefreshToken(admin.ID)
// 保存 Refresh-Token 到 Redis,設(shè)置過(guò)期時(shí)間
err := l.adminRepo.SaveRefreshToken(
ctx,
admin.ID,
refreshToken,
globals.AppConfig.JWT.RefreshTokenExpiry,
)
if err != nil {
// 記錄日志但不阻斷登錄
globals.Log.Error("SaveRefreshToken err:", err)
}
resp := (&responses.AdminLoginResp{}).ToResponse(admin)
resp.AccessToken = accessToken
resp.RefreshToken = refreshToken
return resp, nil
}
3.2.3 獲取管理員信息
func (l *AdminLoginLogic) GetAdminInfo(ctx context.Context, adminID uint) (*responses.AdminInfoResp, error) {
admin, err := l.adminRepo.FindByIDWithDetails(ctx, adminID)
if err != nil || admin == nil {
return nil, errors.New("管理員不存在")
}
if admin.Status != 1 {
return nil, errors.New("賬號(hào)已被禁用")
}
return (&responses.AdminInfoResp{}).ToResponse(admin), nil
}
3.2.4 登出
func (l *AdminLoginLogic) Logout(ctx context.Context, adminID uint) error {
return l.adminRepo.DeleteRefreshToken(ctx, adminID)
}
3.3 數(shù)據(jù)訪問(wèn)層(Repo)
僅展示關(guān)鍵函數(shù),完整代碼已在文章開(kāi)頭給出。
FindByPhone / FindByAccountID / FindByIDWithDetails
利用 GORM 的Preload一把連表查,減少 N+1SaveRefreshToken / DeleteRefreshToken
使用admin:refresh_token:{id}作為 Redis Key,天然支持「單設(shè)備登錄」或「多端互踢」
四、如何無(wú)感刷新 Access-Token
前端收到 401 Unauthorized 后,攜帶 Refresh-Token 調(diào) /admin/refresh:
// 偽代碼(Handler 略)
refreshToken := c.GetHeader("X-Refresh-Token")
adminID, err := usersignutil.ParseRefreshToken(refreshToken)
if err != nil { /* 無(wú)效 Refresh-Token */ }
// 與 Redis 比對(duì)
saved, _ := repo.GetRefreshToken(ctx, adminID)
if saved != refreshToken { /* 已被踢出 */ }
// 重新頒發(fā)
newAccess, _ := usersignutil.GenerateAccessToken(adminID)
五、安全細(xì)節(jié)
| 細(xì)節(jié) | 實(shí)現(xiàn) |
|---|---|
| 密碼加密 | bcrypt 哈希,不可逆 |
| Token 簽名 | 使用獨(dú)立 jwtSecret,區(qū)分 Access/Refresh |
| Refresh-Token 存儲(chǔ) | Redis + TTL,支持熱踢人 |
| SQL 注入 | GORM 占位符自動(dòng)防注入 |
| 并發(fā)登錄 | Redis Key 覆蓋即可實(shí)現(xiàn)「后者踢前者」 |
六、總結(jié)
本文用 200 行核心代碼展示了:
- 分層架構(gòu):Handler → Logic → Repo
- 雙 Token:Access-Token(短)+ Refresh-Token(長(zhǎng))
- 統(tǒng)一響應(yīng):封裝
response.OK / Fail消除樣板 - 安全退出:Redis 刪除 Refresh-Token 即踢人
到此這篇關(guān)于Go + Gin實(shí)現(xiàn)雙Token管理員登錄的示例代碼的文章就介紹到這了,更多相關(guān)Go Gin 雙Token管理員登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang協(xié)程與線(xiàn)程區(qū)別簡(jiǎn)要介紹
這篇文章主要介紹了golang協(xié)程與線(xiàn)程區(qū)別簡(jiǎn)要介紹,進(jìn)程是操作系統(tǒng)資源分配的基本單位,是程序運(yùn)行的實(shí)例,線(xiàn)程是操作系統(tǒng)調(diào)度到CPU中執(zhí)行的基本單位2022-06-06
go語(yǔ)言規(guī)范RESTful?API業(yè)務(wù)錯(cuò)誤處理
這篇文章主要為大家介紹了go語(yǔ)言規(guī)范RESTful?API業(yè)務(wù)錯(cuò)誤處理方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
gtoken替換jwt實(shí)現(xiàn)sso登錄的排雷避坑
這篇文章主要為大家介紹了gtoken替換jwt實(shí)現(xiàn)sso登錄的排雷避坑,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Golang設(shè)計(jì)模式工廠模式實(shí)戰(zhàn)寫(xiě)法示例詳解
這篇文章主要為大家介紹了Golang 工廠模式實(shí)戰(zhàn)寫(xiě)法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
Go習(xí)慣用法(多值賦值短變量聲明賦值簡(jiǎn)寫(xiě)模式)基礎(chǔ)實(shí)例
本文為大家介紹了Go習(xí)慣用法(多值賦值,短變量聲明和賦值,簡(jiǎn)寫(xiě)模式、多值返回函數(shù)、comma,ok 表達(dá)式、傳值規(guī)則)的基礎(chǔ)實(shí)例,幫大家鞏固扎實(shí)Go語(yǔ)言基礎(chǔ)2024-01-01
Golang 實(shí)現(xiàn)超大文件讀取的兩種方法
這篇文章主要介紹了Golang 實(shí)現(xiàn)超大文件讀取的兩種方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04

