Go + Gin实现双Token管理员登录的示例代码

 更新时间:2025年07月15日 08:46:27   作者:Go Dgg  
本文主要介绍了Go + Gin实现双Token管理员登录的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、为什么要做「双 Token」

传统单 Token(JWT)架构下,“续签”与“强制失效” 是一对矛盾:

单 Token 痛点双 Token 解法
Token 过期后需重新登录,体验差Access-Token 失效后,用 Refresh-Token 无感刷新
Token 一旦泄露,在有效期内无法撤销Refresh-Token 存 Redis,可一键踢人
续签逻辑侵入业务代码续签、校验、踢人全部封装在 Auth 中间件

本文将用 Gin + GORM + Redis 带你落地一套生产可用的「管理员双 Token」登录体系。

二、整体架构

┌────────────┐       ┌──────────────┐       ┌──────────┐
│   Web      │──────►│  Handler     │──────►│  Logic   │
└────────────┘       └──────────────┘       └──────────┘
         ▲                    ▲                      │
         │                    │                      ▼
         │            ┌──────────────┐       ┌──────────┐
         │            │  Response    │       │   Repo   │
         └────────────┘  Helper      │       └──────────┘
                                     │
                              Redis / MySQL
  • Handler 负责参数校验、鉴权前置检查
  • Logic 处理核心业务:登录、刷新、退出、获取用户信息
  • Repo 封装数据访问:MySQL 查管理员、Redis 存 Refresh-Token
  • Response 统一封装返回格式,避免样板代码

三、核心代码走读

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, "参数不合法")
            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)
    }
}
  • 只做「绑定参数 + 调用逻辑 + 包装返回」
  • 不掺杂任何业务判断,保持 单一职责

3.2 业务层(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("账号不存在")
    }
    if admin.Status != 1 {
        return nil, errors.New("账号已被禁用")
    }
    if !usersignutil.CheckPasswordHash(req.Password, admin.Password) {
        return nil, errors.New("密码错误")
    }

    // 更新最后登录时间
    _ = 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,设置过期时间
    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("账号已被禁用")
    }
    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 数据访问层(Repo)

仅展示关键函数,完整代码已在文章开头给出。

  • FindByPhone / FindByAccountID / FindByIDWithDetails
    利用 GORM 的 Preload 一把连表查,减少 N+1

  • SaveRefreshToken / DeleteRefreshToken
    使用 admin:refresh_token:{id} 作为 Redis Key,天然支持「单设备登录」或「多端互踢」

四、如何无感刷新 Access-Token

前端收到 401 Unauthorized 后,携带 Refresh-Token 调 /admin/refresh

// 伪代码(Handler 略)
refreshToken := c.GetHeader("X-Refresh-Token")
adminID, err := usersignutil.ParseRefreshToken(refreshToken)
if err != nil { /* 无效 Refresh-Token */ }

// 与 Redis 比对
saved, _ := repo.GetRefreshToken(ctx, adminID)
if saved != refreshToken { /* 已被踢出 */ }

// 重新颁发
newAccess, _ := usersignutil.GenerateAccessToken(adminID)

五、安全细节

细节实现
密码加密bcrypt 哈希,不可逆
Token 签名使用独立 jwtSecret,区分 Access/Refresh
Refresh-Token 存储Redis + TTL,支持热踢人
SQL 注入GORM 占位符自动防注入
并发登录Redis Key 覆盖即可实现「后者踢前者」

六、总结

本文用 200 行核心代码展示了:

  1. 分层架构:Handler → Logic → Repo
  2. 双 Token:Access-Token(短)+ Refresh-Token(长)
  3. 统一响应:封装 response.OK / Fail 消除样板
  4. 安全退出:Redis 删除 Refresh-Token 即踢人

到此这篇关于Go + Gin实现双Token管理员登录的示例代码的文章就介绍到这了,更多相关Go Gin 双Token管理员登录内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • golang协程与线程区别简要介绍

    golang协程与线程区别简要介绍

    这篇文章主要介绍了golang协程与线程区别简要介绍,进程是操作系统资源分配的基本单位,是程序运行的实例,线程是操作系统调度到CPU中执行的基本单位
    2022-06-06
  • go语言规范RESTful API业务错误处理

    go语言规范RESTful API业务错误处理

    这篇文章主要为大家介绍了go语言规范RESTful API业务错误处理方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • gtoken替换jwt实现sso登录的排雷避坑

    gtoken替换jwt实现sso登录的排雷避坑

    这篇文章主要为大家介绍了gtoken替换jwt实现sso登录的排雷避坑,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Golang设计模式工厂模式实战写法示例详解

    Golang设计模式工厂模式实战写法示例详解

    这篇文章主要为大家介绍了Golang 工厂模式实战写法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Go习惯用法(多值赋值短变量声明赋值简写模式)基础实例

    Go习惯用法(多值赋值短变量声明赋值简写模式)基础实例

    本文为大家介绍了Go习惯用法(多值赋值,短变量声明和赋值,简写模式、多值返回函数、comma,ok 表达式、传值规则)的基础实例,帮大家巩固扎实Go语言基础
    2024-01-01
  • Go java 算法之括号生成示例详解

    Go java 算法之括号生成示例详解

    这篇文章主要为大家介绍了Go java 算法之括号生成示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Golang 实现超大文件读取的两种方法

    Golang 实现超大文件读取的两种方法

    这篇文章主要介绍了Golang 实现超大文件读取的两种方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • golang内置函数len的小技巧

    golang内置函数len的小技巧

    len是很常用的内置函数,可以测量字符串、slice、array、channel以及map的长度/元素个数。本文就来介绍一下其他小技巧,感兴趣的可以了解一下
    2021-07-07
  • Go设计模式之状态模式图文详解

    Go设计模式之状态模式图文详解

    状态模式是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样,本文将通过一些图片来给大家详细的介绍一下Go的状态模式,需要的朋友可以参考下
    2023-08-08
  • go语言切片去重的3种方式

    go语言切片去重的3种方式

    go语言中的切片是使用非常频繁的一个数据结构,本文主要介绍了go语言切片去重的3种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-08-08

最新评论