MCPcopy
hub / github.com/appleboy/gin-jwt

github.com/appleboy/gin-jwt @v3.5.1 sqlite

repository ↗ · DeepWiki ↗ · release v3.5.1 ↗
295 symbols 1,122 edges 22 files 105 documented · 36%
README

Gin JWT 中间件

English | 繁體中文 | 简体中文

Run Tests Trivy Security Scan GitHub tag GoDoc Go Report Card codecov Sourcegraph

一个强大且灵活的 Gin Web 框架的 JWT 认证中间件,基于 golang-jwt/jwt 实现。 轻松为你的 Gin 应用添加登录、Token 刷新与授权功能。


目录


功能特色

  • 🔒 为 Gin 提供简单的 JWT 认证
  • 🔁 内置登录、刷新、登出处理器
  • 🛡️ 可自定义认证、授权与 Claims
  • 🍪 支持 Cookie 与 Header Token
  • 📝 易于集成,API 清晰
  • 🔐 符合 RFC 6749 规范的刷新令牌(OAuth 2.0 标准)
  • 🗄️ 可插拔的刷新令牌存储(内存、Redis 客户端缓存)
  • 🏭 直接生成 Token,无需 HTTP 中间件
  • 📦 结构化 Token 类型与元数据

安全性注意事项

🔒 关键安全要求

⚠️ JWT 密钥安全

  • 最低要求: 使用至少 256 位(32 字节) 长度的密钥
  • 禁止使用: 简单密码、字典词汇或可预测的模式
  • 建议: 生成加密安全的随机密钥或使用 RS256 算法
  • 存储: 将密钥存储在环境变量中,绝不硬编码在源码中
  • 漏洞: 弱密钥易受暴力破解攻击(jwt-cracker

🛡️ 生产环境安全检查清单

  • 仅限 HTTPS: 生产环境中务必使用 HTTPS
  • 强密钥: 最少 256 位随机生成的密钥
  • Token 过期: 设置适当的过期时间(建议:访问 Token 15-60 分钟)
  • 安全 Cookie: 启用 SecureCookieCookieHTTPOnly 和适当的 SameSite 设置
  • 环境变量: 将敏感配置存储在环境变量中
  • 输入验证: 彻底验证所有认证输入

🔄 OAuth 2.0 安全标准

此库遵循 RFC 6749 OAuth 2.0 安全标准:

  • 分离令牌: 使用不同的不透明刷新令牌(非 JWT)以增强安全性
  • 服务器端存储: 刷新令牌在服务器端存储和验证
  • 令牌轮替: 每次使用时自动轮替刷新令牌
  • 增强安全性: 防止 JWT 刷新令牌漏洞和重放攻击

💡 安全配置示例

// ❌ 不良:弱密钥、不安全设置
authMiddleware := &jwt.GinJWTMiddleware{
    Key:         []byte("weak"),           // 太短!
    Timeout:     time.Hour * 24,          // 太长!
    SecureCookie: false,                  // 生产环境不安全!
}

// ✅ 良好:强安全配置
authMiddleware := &jwt.GinJWTMiddleware{
    Key:            []byte(os.Getenv("JWT_SECRET")), // 来自环境变量
    Timeout:        time.Minute * 15,                // 短期访问令牌
    MaxRefresh:     time.Hour * 24 * 7,             // 1 周刷新有效期
    SecureCookie:   true,                           // 仅限 HTTPS
    CookieHTTPOnly: true,                           // 防止 XSS
    CookieSameSite: http.SameSiteStrictMode,        // CSRF 保护
    SendCookie:     true,                           // 启用安全 Cookie
}

更多安全指导,请参见我们的 安全最佳实践指南


安装

需要 Go 1.24+

go get -u github.com/appleboy/gin-jwt/v3
import "github.com/appleboy/gin-jwt/v3"

快速开始示例

请参考 _example/basic/server.go 示例文件,并可使用 ExtractClaims 获取 JWT 内的用户数据。

package main

import (
  "log"
  "net/http"
  "os"
  "time"

  jwt "github.com/appleboy/gin-jwt/v3"
  "github.com/gin-gonic/gin"
  "github.com/golang-jwt/jwt/v5"
)

type login struct {
  Username string `form:"username" json:"username" binding:"required"`
  Password string `form:"password" json:"password" binding:"required"`
}

var (
  identityKey = "id"
  port        string
)

// User demo
type User struct {
  UserName  string
  FirstName string
  LastName  string
}

func init() {
  port = os.Getenv("PORT")
  if port == "" {
    port = "8000"
  }
}

func main() {
  engine := gin.Default()
  // the jwt middleware
  authMiddleware, err := jwt.New(initParams())
  if err != nil {
    log.Fatal("JWT Error:" + err.Error())
  }

  // initialize middleware
  errInit := authMiddleware.MiddlewareInit()
  if errInit != nil {
    log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error())
  }

  // register route
  registerRoute(engine, authMiddleware)

  // start http server
  if err = http.ListenAndServe(":"+port, engine); err != nil {
    log.Fatal(err)
  }
}

func registerRoute(r *gin.Engine, handle *jwt.GinJWTMiddleware) {
  // Public routes
  r.POST("/login", handle.LoginHandler)
  r.POST("/refresh", handle.RefreshHandler) // RFC 6749 compliant refresh endpoint

  r.NoRoute(handle.MiddlewareFunc(), handleNoRoute())

  // Protected routes
  auth := r.Group("/auth", handle.MiddlewareFunc())
  auth.GET("/hello", helloHandler)
  auth.POST("/logout", handle.LogoutHandler) // Logout with refresh token revocation
}

func initParams() *jwt.GinJWTMiddleware {
  return &jwt.GinJWTMiddleware{
    Realm:       "test zone",
    Key:         []byte("secret key"),
    Timeout:     time.Hour,
    MaxRefresh:  time.Hour,
    IdentityKey: identityKey,
    PayloadFunc: payloadFunc(),

    IdentityHandler: identityHandler(),
    Authenticator:   authenticator(),
    Authorizer:      authorizer(),
    Unauthorized:    unauthorized(),
    LogoutResponse:  logoutResponse(),
    TokenLookup:     "header: Authorization, query: token, cookie: jwt",
    // TokenLookup: "query:token",
    // TokenLookup: "cookie:token",
    TokenHeadName: "Bearer",
    TimeFunc:      time.Now,
  }
}

func payloadFunc() func(data any) jwt.MapClaims {
  return func(data any) jwt.MapClaims {
    if v, ok := data.(*User); ok {
      return jwt.MapClaims{
        identityKey: v.UserName,
      }
    }
    return jwt.MapClaims{}
  }
}

func identityHandler() func(c *gin.Context) any {
  return func(c *gin.Context) any {
    claims := jwt.ExtractClaims(c)
    return &User{
      UserName: claims[identityKey].(string),
    }
  }
}

func authenticator() func(c *gin.Context) (any, error) {
  return func(c *gin.Context) (any, error) {
    var loginVals login
    if err := c.ShouldBind(&loginVals); err != nil {
      return "", jwt.ErrMissingLoginValues
    }
    userID := loginVals.Username
    password := loginVals.Password

    if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") {
      return &User{
        UserName:  userID,
        LastName:  "Bo-Yi",
        FirstName: "Wu",
      }, nil
    }
    return nil, jwt.ErrFailedAuthentication
  }
}

func authorizer() func(c *gin.Context, data any) bool {
  return func(c *gin.Context, data any) bool {
    if v, ok := data.(*User); ok && v.UserName == "admin" {
      return true
    }
    return false
  }
}

func unauthorized() func(c *gin.Context, code int, message string) {
  return func(c *gin.Context, code int, message string) {
    c.JSON(code, gin.H{
      "code":    code,
      "message": message,
    })
  }
}

func logoutResponse() func(c *gin.Context) {
  return func(c *gin.Context) {
    // This demonstrates that claims are now accessible during logout
    claims := jwt.ExtractClaims(c)
    user, exists := c.Get(identityKey)

    response := gin.H{
      "code":    http.StatusOK,
      "message": "Successfully logged out",
    }

    // Show that we can access user information during logout
    if len(claims) > 0 {
      response["logged_out_user"] = claims[identityKey]
    }
    if exists {
      response["user_info"] = user.(*User).UserName
    }

    c.JSON(http.StatusOK, response)
  }
}

func handleNoRoute() func(c *gin.Context) {
  return func(c *gin.Context) {
    c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
  }
}

func helloHandler(c *gin.Context) {
  claims := jwt.ExtractClaims(c)
  user, _ := c.Get(identityKey)
  c.JSON(200, gin.H{
    "userID":   claims[identityKey],
    "userName": user.(*User).UserName,
    "text":     "Hello World.",
  })
}

使用示例

本项目提供多个完整的示例实现,展示不同的使用场景:

🔑 基础认证

展示基本的 JWT 认证功能,包含登录、受保护路由和 token 验证。

🌐 OAuth SSO 集成

OAuth 2.0 单点登录示例,支持多个身份提供者(Google、GitHub):

  • OAuth 2.0 授权码流程
  • 使用 state token 的 CSRF 保护
  • 双重认证支持:httpOnly cookies + Authorization headers
  • 为浏览器和移动应用程序提供安全的 token 传递
  • 包含交互式 demo 页面

🔐 Token 生成器

直接生成 JWT token,无需 HTTP middleware,适用于:

  • 程序化认证
  • 服务间通信
  • 测试需要认证的端点
  • 自定义认证流程

🗄️ Redis 存储

展示 Redis 集成用于 refresh token 存储,包含:

  • 客户端缓存以提升性能
  • 自动降级至内存存储
  • 生产环境就绪的配置示例

🛡️ 授权控制

高级授权模式,包含:

  • 基于角色的访问控制
  • 基于路径的授权
  • 多个 middleware 实例
  • 精细的权限控制

配置

GinJWTMiddleware 结构体提供以下配置选项:

选项 类型 必填 默认值 描述
Realm string "gin jwt" 显示给用户的 Realm 名称。
SigningAlgorithm string "HS256" 签名算法 (HS256, HS384, HS512, RS256, RS384, RS512)。
Key []byte - 用于签名的密钥。
Timeout time.Duration time.Hour JWT Token 的有效期。
MaxRefresh time.Duration 0 刷新 Token 的有效期。
Authenticator func(c *gin.Context) (any, error) - 验证用户的回调函数。返回用户数据。
Authorizer func(c *gin.Context, data any) bool true 授权已验证用户的回调函数。
PayloadFunc func(data any) jwt.MapClaims - 向 Token 添加额外 Payload 数据的回调函数。
Unauthorized func(c *gin.Context, code int, message string) - 处理未授权请求的回调函数。
LoginResponse func(c *gin.Context, token *core.Token) - 处理成功登录响应的回调函数。
LogoutResponse func(c *gin.Context) - 处理成功登出响应的回调函数。
RefreshResponse func(c *gin.Context, token *core.Token) - 处理成功刷新响应的回调函数。
IdentityHandler func(*gin.Context) any - 从 Claims 检索身份的回调函数。
IdentityKey string "identity" 用于在 Claims 中存储身份的键。
TokenLookup string `"header:Auth

Extension points exported contracts — how you extend this code

TokenStore (Interface)
TokenStore defines the interface for storing and retrieving refresh tokens [2 implementers]
core/store.go
RedisOption (FuncType)
RedisOption defines a function type for configuring Redis store
auth_jwt_redis.go

Core symbols most depended-on inside this repo

Get
called by 77
core/store.go
New
called by 66
auth_jwt.go
Set
called by 30
core/store.go
EnableRedisStore
called by 22
auth_jwt_redis.go
NewInMemoryRefreshTokenStore
called by 21
store/memory.go
Set
called by 19
store/memory.go
MiddlewareFunc
called by 16
auth_jwt.go
WithRedisAddr
called by 13
auth_jwt_redis.go

Shape

Function 212
Method 60
Struct 20
FuncType 1
Interface 1
TypeAlias 1

Languages

Go100%

Modules by API surface

auth_jwt_test.go56 symbols
auth_jwt.go39 symbols
_example/oauth_sso/server.go28 symbols
_example/authorization/main.go22 symbols
store/memory_test.go20 symbols
auth_jwt_cookie_test.go15 symbols
store/factory.go14 symbols
_example/basic/server.go14 symbols
store/redis.go13 symbols
auth_jwt_redis_test.go11 symbols
store/redis_test.go10 symbols
core/store.go10 symbols

Used by 3 indexed graphs manifest dependencies, hub-wide

Dependencies from manifests, versioned

cloud.google.com/go/compute/metadatav0.9.0 · 1×
dario.cat/mergov1.0.2 · 1×
github.com/Azure/go-ansitermv0.0.0-2025010203350 · 1×
github.com/Microsoft/go-winiov0.6.2 · 1×
github.com/appleboy/gofight/v2v2.1.2 · 1×
github.com/bytedance/gopkgv0.1.3 · 1×
github.com/bytedance/sonicv1.14.1 · 1×
github.com/bytedance/sonic/loaderv0.3.0 · 1×
github.com/cespare/xxhash/v2v2.3.0 · 1×
github.com/cloudwego/base64xv0.1.6 · 1×

For agents

$ claude mcp add gin-jwt \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact