# go-starter3 **Repository Path**: exi-red/go-starter3 ## Basic Information - **Project Name**: go-starter3 - **Description**: Golang 项目快速启动器 - **Primary Language**: Go - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-11-12 - **Last Updated**: 2026-01-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Go-Starter JWT 中间件 一个基于 Gin 框架的 JWT 中间件,提供完整的用户认证和授权功能。 ## 功能特性 - ✅ 基于 Gin 框架的 JWT 中间件 - ✅ 支持多种 Token 来源(Header、Query、Cookie) - ✅ 支持 Token 黑名单,实现 Token 吊销 - ✅ 提供完整的 Token 生成、验证、刷新功能 - ✅ 支持自定义错误处理和配置选项 - ✅ 集成项目现有的错误处理机制 - ✅ 支持设备指纹绑定 - ✅ 支持密钥轮换 - ✅ 支持登录频率限制 - ✅ 支持用户锁定机制 - ✅ 提供完整的会话管理功能 ## 安装 ```bash go get -u gitee.com/exi-red/go-starter/starter/gjwt ``` ## 快速开始 ### 1. 配置 JWT ```go import "gitee.com/exi-red/go-starter/starter/gjwt" // 创建 JWT 配置 config := &gjwt.JWTConfig{ SecretKeys: []string{"your-super-secret-jwt-key-change-in-production"}, AccessTokenExpire: time.Hour * 24, RefreshTokenExpire: time.Hour * 24 * 7, Issuer: "go-starter", EnableDeviceBinding: true, } ``` ### 2. 创建 JWT 管理器 ```go import ( "gitee.com/exi-red/go-starter/starter/gjwt" "gitee.com/exi-red/go-starter/starter/glog" "github.com/redis/go-redis/v9" ) // 创建 Redis 客户端 redisClient := redis.NewClient(&redis.Options{ Addr: "localhost:6379", }) // 创建 Token 黑名单 blacklist := gjwt.NewRedisTokenBlacklist(redisClient, "jwt:blacklist:") // 创建 JWT 管理器 manager, err := gjwt.NewJWTManager(config, glog.App, blacklist, nil) if err != nil { // 处理错误 } ``` ### 3. 集成到 Gin 中 #### 3.1 基本用法 ```go import ( "gitee.com/exi-red/go-starter/starter/ghttp" "gitee.com/exi-red/go-starter/starter/gjwt" "github.com/gin-gonic/gin" ) // 创建 Gin 路由 r := gin.Default() // 添加 JWT 中间件 r.Use(gjwt.JWTAuth(manager, ghttp.ServerInjector)) // 定义受保护的路由 r.GET("/api/protected", func(c *gin.Context) { // 从上下文中获取用户信息 claims, exists := gjwt.GetUserClaims(c) if !exists { // 处理错误 } // 使用用户信息处理业务逻辑 c.JSON(200, gin.H{ "user_id": claims.UserID, "message": "Hello, protected resource!", }) }) ``` #### 3.2 自定义配置选项 ```go // 自定义 JWT 中间件配置 r.Use(gjwt.JWTAuth(manager, ghttp.ServerInjector, gjwt.WithTokenSource(gjwt.TokenSourceHeader), gjwt.WithContextKey("user_claims"), gjwt.WithHeaderPrefix("Bearer"), gjwt.WithSkipPaths([]string{ "/api/login", "/api/register", "/api/public", }), )) ``` #### 3.3 从不同来源提取 Token ```go // 从 Query 参数获取 Token r.Use(gjwt.JWTAuth(manager, ghttp.ServerInjector, gjwt.WithTokenSource(gjwt.TokenSourceQuery), gjwt.WithQueryKey("auth_token"), )) // 从 Cookie 获取 Token r.Use(gjwt.JWTAuth(manager, ghttp.ServerInjector, gjwt.WithTokenSource(gjwt.TokenSourceCookie), gjwt.WithCookieKey("jwt_token"), )) ``` ### 4. 登录和认证 #### 4.1 创建认证处理器 ```go import ( "gitee.com/exi-red/go-starter/starter/gjwt" "gitee.com/exi-red/go-starter/starter/glog" "github.com/gin-gonic/gin" ) // 创建认证处理器 authHandler := gjwt.NewAuthHandler( manager, glog.App, // 提供用户验证函数 func(username, password string) (string, map[string]interface{}, error) { // 实际应用中,这里应该查询数据库验证用户凭据 if username == "admin" && password == "password" { return "user123", map[string]interface{}{ "role": "admin", "name": "管理员", }, nil } return "", nil, fmt.Errorf("invalid credentials") }, ) ``` #### 4.2 注册认证路由 ```go // 注册认证路由 authGroup := r.Group("/api/auth") { authGroup.POST("/login", authHandler.HandleLogin) authGroup.POST("/logout", authHandler.HandleLogout) authGroup.POST("/refresh", authHandler.HandleRefreshToken) authGroup.POST("/force-logout", authHandler.HandleForceLogout) authGroup.GET("/sessions", authHandler.HandleGetUserSessions) authGroup.POST("/kickout", authHandler.HandleKickoutOtherDevices) } ``` ### 5. 会话管理 #### 5.1 创建会话管理器 ```go import ( "gitee.com/exi-red/go-starter/starter/gjwt" "gitee.com/exi-red/go-starter/starter/glog" "github.com/redis/go-redis/v9" ) // 创建 Redis Token 存储 redisClient := redis.NewClient(&redis.Options{ Addr: "localhost:6379", }) tokenStore := gjwt.NewRedisTokenStore(redisClient, "jwt:token:") // 创建会话管理器 config := &gjwt.SessionConfig{ TokenStore: tokenStore, Blacklist: blacklist, Logger: glog.App, } sessionManager, err := gjwt.NewSessionManager(config) if err != nil { // 处理错误 } ``` #### 5.2 使用会话管理器 ```go // 获取用户会话 sessions, err := sessionManager.GetUserSessions("user123") // 强制登出用户 err := sessionManager.ForceLogoutUser("user123") // 踢除其他设备 err := sessionManager.KickoutOtherDevices("user123", "device123") // 批量登出用户 err := sessionManager.BatchLogoutUsers([]string{"user123", "user456"}) ``` ## 配置选项 ### JWTConfig | 字段 | 类型 | 描述 | 默认值 | | --------------------- | --------------- | ----------------------- | ---------------------------------------------------- | | `SecretKeys` | `[]string` | JWT 密钥列表 | `["your-super-secret-jwt-key-change-in-production"]` | | `CurrentKeyIndex` | `int` | 当前使用的密钥索引 | `0` | | `AccessTokenExpire` | `time.Duration` | Access Token 过期时间 | `24h` | | `RefreshTokenExpire` | `time.Duration` | Refresh Token 过期时间 | `7d` | | `Issuer` | `string` | Token 发行者 | `"go-starter"` | | `EnableDeviceBinding` | `bool` | 是否启用设备指纹绑定 | `false` | | `EnableKeyRotation` | `bool` | 是否启用密钥轮换 | `false` | | `KeyRotationInterval` | `time.Duration` | 密钥轮换间隔 | `24h` | | `TokenMaxIdleTime` | `time.Duration` | Token 最大空闲时间 | `0`(不限制) | | `MaxFailedAttempts` | `int` | 最大失败登录次数 | `5` | | `LockoutDuration` | `time.Duration` | 登录失败锁定时间 | `30m` | | `LoginRateLimit` | `int` | 登录频率限制(次/分钟) | `10` | ### 中间件配置选项 | 选项 | 描述 | 默认值 | | ------------------ | ------------------------------ | ------------------- | | `WithTokenSource` | 设置 Token 来源 | `TokenSourceHeader` | | `WithContextKey` | 设置上下文中存储用户信息的键名 | `"jwt_user_claims"` | | `WithErrorHandler` | 设置自定义错误处理函数 | 默认错误处理 | | `WithQueryKey` | 设置 Query 参数中的 Token 键名 | `"token"` | | `WithCookieKey` | 设置 Cookie 中的 Token 键名 | `"token"` | | `WithHeaderPrefix` | 设置 Authorization 头中的前缀 | `"Bearer"` | | `WithSkipPaths` | 设置需要跳过 JWT 验证的路径 | `[]` | ## 从上下文中获取用户信息 ```go // 在路由处理函数中获取用户信息 func protectedHandler(c *gin.Context) { // 获取用户信息 claims, exists := gjwt.GetUserClaims(c) if !exists { // 处理错误 } // 使用用户信息 userID := claims.UserID role := claims.Claims["role"] // 处理业务逻辑 } ``` ## 错误处理 ### 错误代码 | 错误代码 | 描述 | | -------- | -------------------- | | 4001 | 无效的 Token | | 4002 | Token 已过期 | | 4003 | Token 已被列入黑名单 | | 4004 | 缺少 Token | | 4005 | 认证失败 | | 4006 | JWT 内部错误 | | 4007 | 无效的 JWT 密钥 | | 4008 | 无效的 Token 声明 | ### 自定义错误处理 ```go // 自定义错误处理 r.Use(gjwt.JWTAuth(manager, ghttp.ServerInjector, gjwt.WithErrorHandler(func(c *gin.Context, err gerror.IErrorCode) { c.JSON(401, gin.H{ "code": err.Code(), "message": "Custom error message: " + err.Human(), }) }), )) ``` ## 最佳实践 ### 安全性 1. **使用强密钥**:确保 JWT 密钥足够复杂,建议使用至少 32 个字符 2. **设置合理的过期时间**:Access Token 建议设置较短的过期时间(如 15 分钟),Refresh Token 可以设置较长的过期时间(如 7 天) 3. **使用 HTTPS**:在生产环境中,始终使用 HTTPS 传输 JWT Token 4. **实现 Token 黑名单**:支持 Token 吊销,将已注销或被盗用的 Token 加入黑名单 5. **不要在 Token 中存储敏感信息**:只存储必要的用户标识信息,不要存储密码等敏感数据 6. **启用设备绑定**:限制 Token 只能在特定设备上使用 7. **启用密钥轮换**:定期轮换 JWT 密钥,提高安全性 ### 性能 1. **使用 Redis 存储黑名单**:对于大规模应用,建议使用 Redis 存储 Token 黑名单,以提高查询性能 2. **避免频繁刷新 Token**:只有在 Access Token 即将过期时才刷新 Token 3. **合理设置 Token 过期时间**:避免设置过短的过期时间,导致用户频繁重新登录 ### 开发体验 1. **提供清晰的错误信息**:在 Token 验证失败时,返回清晰的错误信息,便于客户端调试 2. **支持多种 Token 来源**:支持从 Header、Query 参数、Cookie 等多种来源获取 Token 3. **提供完整的文档和示例**:便于开发者快速集成和使用 ## 完整示例 ### 服务端示例 ```go package main import ( "fmt" "net/http" "time" "gitee.com/exi-red/go-starter/errors/gcode" "gitee.com/exi-red/go-starter/errors/gerror" "gitee.com/exi-red/go-starter/starter/ghttp" "gitee.com/exi-red/go-starter/starter/gjwt" "gitee.com/exi-red/go-starter/starter/glog" "github.com/gin-gonic/gin" "github.com/redis/go-redis/v9" ) func main() { // 初始化日志 glog.Init() // 创建 Redis 客户端 redisClient := redis.NewClient(&redis.Options{ Addr: "localhost:6379", }) // 创建 Token 黑名单 blacklist := gjwt.NewRedisTokenBlacklist(redisClient, "jwt:blacklist:") // 创建 JWT 配置 config := &gjwt.JWTConfig{ SecretKeys: []string{"your-super-secret-jwt-key-change-in-production"}, AccessTokenExpire: time.Hour * 24, RefreshTokenExpire: time.Hour * 24 * 7, Issuer: "go-starter", EnableDeviceBinding: true, } // 创建 JWT 管理器 manager, err := gjwt.NewJWTManager(config, glog.App, blacklist, nil) if err != nil { glog.App.Fatal(err) } // 创建 Gin 路由 r := gin.Default() // 登录路由(无需认证) r.POST("/api/login", func(c *gin.Context) { // 模拟用户验证 var loginReq struct { Username string `json:"username"` Password string `json:"password"` } if err := c.ShouldBindJSON(&loginReq); err != nil { ghttp.Error(c, gcode.JWTAuthFailed) return } // 模拟用户验证(实际应用中应查询数据库) if loginReq.Username != "admin" || loginReq.Password != "password" { ghttp.Error(c, gcode.JWTAuthFailed) return } // 生成 Token ctx := c.Request.Context() accessToken, err := manager.GenerateAccessToken(ctx, "user123", map[string]interface{}{ "role": "admin", "name": "管理员", }, "device123", "127.0.0.1") if err != nil { ghttp.Error(c, gcode.JWTInternalError) return } refreshToken, err := manager.GenerateRefreshToken(ctx, "user123", "device123", "127.0.0.1") if err != nil { ghttp.Error(c, gcode.JWTInternalError) return } // 返回 Token ghttp.Success(c, map[string]interface{}{ "access_token": accessToken, "refresh_token": refreshToken, "token_type": "Bearer", "expires_in": int64(time.Hour * 24 / time.Second), }) }) // 受保护的路由组(需要 JWT 认证) protected := r.Group("/api/protected") protected.Use(gjwt.JWTAuth(manager, ghttp.ServerInjector)) { protected.GET("/user", func(c *gin.Context) { // 从上下文中获取用户信息 claims, exists := gjwt.GetUserClaims(c) if !exists { ghttp.Error(c, gcode.JWTAuthFailed) return } ghttp.Success(c, map[string]interface{}{ "user_id": claims.UserID, "name": claims.Claims["name"], "role": claims.Claims["role"], }) }) } // 启动服务器 fmt.Println("Server starting on :8080") if err := r.Run(":8080"); err != nil { glog.App.Fatal(err) } } ``` ### 客户端示例 ```javascript // 登录 async function login() { const response = await fetch('http://localhost:8080/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: 'admin', password: 'password', }), }); const data = await response.json(); if (data.success) { // 存储 Token localStorage.setItem('access_token', data.access_token); localStorage.setItem('refresh_token', data.refresh_token); } } // 访问受保护资源 async function getProtectedResource() { const accessToken = localStorage.getItem('access_token'); const response = await fetch('http://localhost:8080/api/protected/user', { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, }, }); const data = await response.json(); console.log(data); } // 刷新 Token async function refreshToken() { const refreshToken = localStorage.getItem('refresh_token'); const response = await fetch('http://localhost:8080/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refresh_token: refreshToken, }), }); const data = await response.json(); if (data.success) { // 更新 Token localStorage.setItem('access_token', data.access_token); } } // 登出 async function logout() { const accessToken = localStorage.getItem('access_token'); await fetch('http://localhost:8080/api/auth/logout', { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, }, }); // 清除 Token localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); } ``` ## 项目结构 ``` starter/gjwt/ ├── gjwt_auth_handler.go # 认证处理器 ├── gjwt_blacklist.go # Token 黑名单 ├── gjwt_config.go # JWT 配置 ├── gjwt_gin_middleware.go # Gin JWT 中间件 ├── gjwt_manager.go # JWT 管理器 ├── gjwt_session_manager.go # 会话管理器 └── gjwt_token_store.go # Token 存储 ``` ## 许可证 MIT ## 贡献 欢迎提交 Issue 和 Pull Request! ## 联系方式 如有问题,请通过以下方式联系我们: - 项目地址:https://gitee.com/exi-red/go-starter - 邮箱:[项目维护者邮箱] ## 更新日志 ### v1.0.0 - ✅ 初始版本发布 - ✅ 实现基于 Gin 的 JWT 中间件 - ✅ 支持多种 Token 来源 - ✅ 支持 Token 黑名单 - ✅ 提供完整的 Token 生成、验证、刷新功能 - ✅ 支持自定义错误处理和配置选项 - ✅ 集成项目现有的错误处理机制 - ✅ 支持设备指纹绑定 - ✅ 支持密钥轮换 - ✅ 支持登录频率限制 - ✅ 支持用户锁定机制 - ✅ 提供完整的会话管理功能