# JWT 工具使用说明 这是一个基于 Go-Zero 框架的 JWT 工具,支持 token 的生成、验证、刷新和管理功能,并将 token 存储在 Redis 中。 ## 功能特性 - ✅ JWT token 生成和验证 - ✅ Redis 存储管理 - ✅ Token 刷新机制 - ✅ 用户多 token 管理 - ✅ 批量 token 操作 - ✅ 完整的错误处理 - ✅ 性能优化 - ✅ 完整的测试覆盖 ## 依赖说明 ```bash # 主要依赖 go get github.com/golang-jwt/jwt/v5 go get github.com/redis/go-redis/v9 # 测试依赖 go get github.com/stretchr/testify ``` ## 配置说明 ### 1. 配置文件 (usercenter.yaml) ```yaml Auth: AccessSecret: "your-secret-key" AccessExpire: 604800 # 7天过期时间(秒) ``` ### 2. 配置结构体 (config.go) ```go type Auth struct { AccessSecret string // JWT 密钥 AccessExpire int64 // JWT 过期时间(秒) } ``` ## 基本使用方法 ### 1. 初始化 JWT 工具 ```go import ( "backend/utils" "github.com/redis/go-redis/v9" ) // 创建 Redis 客户端 redisClient := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }) // 创建 JWT 工具实例 jwtUtil := utils.NewJWTUtil( "your-secret-key", // 访问密钥 7*24*60*60, // 过期时间(秒) redisClient, // Redis 客户端 ) ``` ### 2. 生成 Token ```go ctx := context.Background() userID := int64(123) username := "john_doe" token, err := jwtUtil.GenerateToken(ctx, userID, username) if err != nil { log.Printf("生成 token 失败: %v", err) return } fmt.Printf("生成的 token: %s\n", token) ``` ### 3. 验证 Token ```go claims, err := jwtUtil.ValidateToken(ctx, token) if err != nil { log.Printf("验证 token 失败: %v", err) return } fmt.Printf("用户ID: %d, 用户名: %s\n", claims.UserID, claims.Username) ``` ### 4. 刷新 Token ```go newToken, err := jwtUtil.RefreshToken(ctx, oldToken) if err != nil { log.Printf("刷新 token 失败: %v", err) return } fmt.Printf("新的 token: %s\n", newToken) ``` ### 5. 删除 Token (登出) ```go err := jwtUtil.DeleteToken(ctx, token) if err != nil { log.Printf("删除 token 失败: %v", err) return } fmt.Println("登出成功") ``` ## 高级功能 ### 1. 管理用户多个 Token ```go // 获取用户所有 token tokens, err := jwtUtil.GetUserTokens(ctx, userID) if err != nil { log.Printf("获取用户 token 失败: %v", err) return } fmt.Printf("用户拥有 %d 个 token\n", len(tokens)) // 删除用户所有 token (强制登出) err = jwtUtil.DeleteAllUserTokens(ctx, userID) if err != nil { log.Printf("删除用户所有 token 失败: %v", err) return } ``` ### 2. 检查 Token 是否存在 ```go exists, err := jwtUtil.TokenExists(ctx, token) if err != nil { log.Printf("检查 token 失败: %v", err) return } if exists { fmt.Println("Token 有效") } else { fmt.Println("Token 不存在或已过期") } ``` ### 3. 解析 Token (不验证签名) ```go claims, err := jwtUtil.ParseTokenUnverified(token) if err != nil { log.Printf("解析 token 失败: %v", err) return } fmt.Printf("Token 中的用户ID: %d\n", claims.UserID) ``` ## 在 Go-Zero 服务中集成 ### 1. 更新服务上下文 ```go // internal/svc/servicecontext.go type ServiceContext struct { Config config.Config JWTUtil *utils.JWTUtil // ... 其他字段 } func NewServiceContext(c config.Config) *ServiceContext { // 创建 Redis 客户端 redisClient := redis.NewClient(&redis.Options{ Addr: "localhost:6379", DB: 0, }) // 创建 JWT 工具 jwtUtil := utils.NewJWTUtil( c.Auth.AccessSecret, c.Auth.AccessExpire, redisClient, ) return &ServiceContext{ Config: c, JWTUtil: jwtUtil, } } ``` ### 2. 在登录逻辑中使用 ```go // internal/logic/loginlogic.go func (l *LoginLogic) Login(in *pb.LoginRequest) (*pb.LoginResponse, error) { // 验证用户凭证... // 生成 JWT token token, err := l.svcCtx.JWTUtil.GenerateToken( l.ctx, user.ID, user.Username, ) if err != nil { return nil, err } return &pb.LoginResponse{ Token: token, User: user, }, nil } ``` ### 3. 在需要认证的逻辑中使用 ```go // internal/logic/getprofilelogic.go func (l *GetProfileLogic) GetProfile(in *pb.GetProfileRequest) (*pb.GetProfileResponse, error) { // 验证 token claims, err := l.svcCtx.JWTUtil.ValidateToken(l.ctx, in.Token) if err != nil { return nil, errors.New("无效的 token") } // 使用 claims 中的用户信息 userID := claims.UserID // 获取用户信息... return &pb.GetProfileResponse{ User: user, }, nil } ``` ## 中间件示例 ```go // JWT 认证中间件 func JWTAuthMiddleware(jwtUtil *utils.JWTUtil) func(next http.HandlerFunc) http.HandlerFunc { return func(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // 从 Header 中获取 token tokenString := r.Header.Get("Authorization") if tokenString == "" { http.Error(w, "缺少 token", http.StatusUnauthorized) return } // 去掉 "Bearer " 前缀 if strings.HasPrefix(tokenString, "Bearer ") { tokenString = tokenString[7:] } // 验证 token claims, err := jwtUtil.ValidateToken(r.Context(), tokenString) if err != nil { http.Error(w, "无效的 token", http.StatusUnauthorized) return } // 将用户信息存储到上下文中 ctx := context.WithValue(r.Context(), "userID", claims.UserID) ctx = context.WithValue(ctx, "username", claims.Username) // 继续处理请求 next(w, r.WithContext(ctx)) } } } ``` ## 错误处理 JWT 工具返回的错误信息包括: - `生成 token 失败`: token 生成过程中的错误 - `存储 token 到 Redis 失败`: Redis 存储错误 - `解析 token 失败`: token 格式错误或签名验证失败 - `无效的 token`: token 无效或已过期 - `token 已过期或不存在`: Redis 中不存在该 token - `token 中的用户ID与 Redis 中的不匹配`: 数据不一致错误 ## 性能优化建议 1. **Redis 连接池**: 使用 Redis 连接池避免频繁创建连接 2. **Token 缓存**: 对于高频访问的 token,可以考虑内存缓存 3. **批量操作**: 使用批量 Redis 操作提高性能 4. **异步删除**: 过期 token 的清理可以异步进行 ## 安全建议 1. **密钥管理**: 使用强密钥并定期轮换 2. **HTTPS**: 始终在 HTTPS 环境中传输 token 3. **过期时间**: 设置合理的 token 过期时间 4. **Redis 安全**: 确保 Redis 访问安全 5. **日志记录**: 记录认证相关的操作日志 ## 测试 运行单元测试: ```bash cd backend/utils go test -v ``` 运行基准测试: ```bash go test -bench=. ``` ## 常见问题 ### Q: 如何处理 token 过期? A: 使用 `RefreshToken` 方法刷新 token,或者重新登录获取新的 token。 ### Q: 如何实现强制登出? A: 使用 `DeleteAllUserTokens` 方法删除用户的所有 token。 ### Q: Redis 连接失败怎么处理? A: 检查 Redis 服务是否正常运行,并确保连接参数正确。 ### Q: Token 验证失败的常见原因? A: - Token 格式错误 - 签名密钥不匹配 - Token 已过期 - Redis 中不存在该 token - 网络连接问题 ## 更新日志 ### v1.0.0 - 基础 JWT 功能实现 - Redis 存储支持 - Token 刷新机制 - 用户多 token 管理 - 完整的测试覆盖