go jwt如何支持refresh-token
在 Go 里用 JWT 支持 refresh token核心思路是用两个 token一个短期有效的access_token用来日常访问接口一个长期有效、专门用来换取新 access_token 的refresh_token。下面是一个标准的设计和实现方案你可以参考。1. 核心设计思路这是“双令牌”机制的基础能让你在不影响用户体验的同时保证安全性。Access Token访问令牌短期有效通常设为 15分钟到2小时。它包含了用户身份信息和权限用于保护 API 接口。Refresh Token刷新令牌长期有效通常设为 24小时到7天。它只用于请求新的 Access Token不应包含业务相关的自定义数据。整体流程像这样登录服务端同时生成 access_token 和 refresh_token将 refresh_token 的哈希值存入数据库绝不存储原始 token然后把两个 token 都返回给客户端。访问客户端每次请求都带上 access_token。服务端验证其有效性通过则放行。刷新当 access_token 过期客户端用 refresh_token 去请求/refresh接口。服务端验证后生成一对全新的 token返回给客户端并废弃旧的 refresh_token。退出直接从数据库中删除或使该用户的 refresh_token 失效。2. 具体实现步骤第一步定义 Token 管理器和结构体首先你需要一个核心结构来管理 token 的生成和验证。可以基于github.com/golang-jwt/jwt/v5这个主流库来封装。goimport ( time github.com/golang-jwt/jwt/v5 ) // JWTManager 负责管理 JWT type JWTManager struct { secretKey []byte accessTokenExpiry time.Duration refreshTokenExpiry time.Duration issuer string } // NewJWTManager 是构造函数 func NewJWTManager(secret string, accessExp, refreshExp time.Duration, issuer string) *JWTManager { return JWTManager{ secretKey: []byte(secret), accessTokenExpiry: accessExp, refreshTokenExpiry: refreshExp, issuer: issuer, } }第二步生成一对 Token登录成功后就调用这个方法同时生成 access_token 和 refresh_token。go// TokenPair 包含返回给客户端的两个 token 和它们的过期时间 type TokenPair struct { AccessToken string json:access_token RefreshToken string json:refresh_token AccessTokenExpiresAt time.Time json:access_token_expires_at RefreshTokenExpiresAt time.Time json:refresh_token_expires_at } // GenerateTokenPair 为给定用户生成一对新 token func (m *JWTManager) GenerateTokenPair(userID string) (*TokenPair, error) { now : time.Now() accessExp : now.Add(m.accessTokenExpiry) refreshExp : now.Add(m.refreshTokenExpiry) // 1. 生成 Access Token (携带用户自定义信息) accessClaims : jwt.RegisteredClaims{ Subject: userID, Issuer: m.issuer, IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(accessExp), } accessToken, err : jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims).SignedString(m.secretKey) if err ! nil { return nil, err } // 2. 生成 Refresh Token (只含标准声明不含业务数据) refreshClaims : jwt.RegisteredClaims{ Subject: userID, Issuer: m.issuer, IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(refreshExp), } refreshToken, err : jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims).SignedString(m.secretKey) if err ! nil { return nil, err } return TokenPair{ AccessToken: accessToken, RefreshToken: refreshToken, AccessTokenExpiresAt: accessExp, RefreshTokenExpiresAt: refreshExp, }, nil }第三步实现刷新逻辑这是最关键的步骤。客户端用 refresh_token 来换取新的 access_token。go// RefreshAccessToken 验证 refresh token 并生成新的 access token func (m *JWTManager) RefreshAccessToken(refreshTokenStr string) (string, error) { // 1. 验证 refresh token 的签名和有效期 token, err : jwt.Parse(refreshTokenStr, func(token *jwt.Token) (interface{}, error) { return m.secretKey, nil }) if err ! nil { return , err // token 无效或过期 } if !token.Valid { return , jwt.ErrTokenInvalid } // 2. 从 token 中提取用户 ID (subject) claims, ok : token.Claims.(jwt.MapClaims) if !ok { return , jwt.ErrTokenInvalid } userID, ok : claims[sub].(string) if !ok || userID { return , jwt.ErrTokenInvalid } // 3. 生成一个全新的 access token newClaims : jwt.RegisteredClaims{ Subject: userID, Issuer: m.issuer, IssuedAt: jwt.NewNumericDate(time.Now()), ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.accessTokenExpiry)), } newAccessToken, err : jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims).SignedString(m.secretKey) if err ! nil { return , err } return newAccessToken, nil }3. 数据库层安全地存储 Refresh Token为了安全和控制强烈建议服务端跟踪每个 refresh_token而不是完全无状态。可以在数据库创建一张sessions或refresh_tokens表字段说明id主键user_id关联的用户 IDrefresh_token_hash存储 refresh_token 的哈希值 (例如 SHA256)用于安全比对expires_attoken 的过期时间revoked_at退出或轮换时标记为已撤销在登录时先计算 refresh_token 的哈希存入refresh_token_hash在刷新时根据客户端传来的 refresh_token 计算出哈希去数据库匹配。这里的关键动作是“令牌轮换Token Rotation”每次刷新时不仅要生成新的 access_token还要生成一个新的 refresh_token并用新的哈希替换掉数据库中旧的记录。这样可以防止一个 refresh_token 被多次使用。go// 在刷新接口的处理函数中伪代码逻辑 func handleRefresh(refreshTokenStr string) { // 1. 计算哈希值并从 DB 查找 hash : sha256.Sum256([]byte(refreshTokenStr)) session, err : db.FindByRefreshHash(hex.EncodeToString(hash[:])) if err ! nil { // token 无效或已撤销 return } // 2. 验证 token 是否过期 (JWT 库会做但数据库也要校验) if session.ExpiresAt.Before(time.Now()) { return } // 3. 生成全新的 token 对 (这就是 token rotation) newPair, err : jwtManager.GenerateTokenPair(session.UserID) // 4. 原子性地在数据库中删除旧记录插入新记录 err db.ReplaceRefreshHash(session.RefreshTokenHash, newPair.RefreshTokenHash) if err ! nil { // 处理错误... } // 5. 将新的 access_token 和 refresh_token 返回给客户端 return newPair }总结一下核心库用github.com/golang-jwt/jwt/v5来创建和验证 JWT。双令牌access_token短期、携带业务信息refresh_token长期、只用于刷新。安全存储服务端存 refresh_token 的哈希值而不是原文。令牌轮换每次刷新都颁发全新的access_tokenrefresh_token对并废弃旧的 refresh_token这是推荐的最佳实践。如果想看一个更完整的项目示例可以参考github.com/Jaro-c/authcore这个库的 auth/jwt 模块它很好地演示了从登录到轮换的完整流程
