简体中文 | English
A collection of production-ready middleware for the Gin web framework.
- JWT Authentication — Token generation, parsing, and Gin middleware with configurable secrets, expiration, and signing methods
- CORS — Configurable cross-origin resource sharing middleware
- Request — JSON binding with defaults applied before Gin validation
- Response — Standardized API response formatting helpers
- Log — Leveled logging interface with pluggable backends
go get github.com/jackman0925/gin-middlewareThis release keeps the existing exported functions and fields, so most applications should continue to compile. It does, however, intentionally change several runtime behaviors for safer production defaults. Review the following items before upgrading:
JWT validation now requires an exp claim by default. Tokens generated by this
library already contain exp and continue to work. If another service or an
older integration issues non-expiring tokens, enable temporary compatibility:
j := jwt.NewWithConfig(jwt.Config{
Secret: "your-secret-key-must-have-32-bytes",
AllowMissingExpiration: true,
})AllowMissingExpiration should be used only during migration. Prefer updating
the token issuer to include exp, then remove this option.
JWT claims are no longer copied into arbitrary Gin Context keys because they could overwrite values written by other middleware. Replace direct access such as:
adminID, exists := c.Get("adminID")with namespaced claim access:
claims, exists := jwt.ClaimsFromContext(c)
if exists {
adminID := claims["adminID"]
}Use jwt.UsernameFromContext(c) for the username claim.
errorhandler.ErrorHandler() now returns internal server error for private
errors instead of exposing err.Error() to clients. Mark only client-safe
errors as public:
c.Error(errors.New("safe client message")).SetType(gin.ErrorTypePublic)Applications that require custom status and message mapping should use
errorhandler.ErrorHandlerWithMapper.
cors.New([]string{"*"}) no longer enables credentials, because browsers reject
the combination of Access-Control-Allow-Origin: * and
Access-Control-Allow-Credentials: true. To allow credentialed requests, list
trusted origins explicitly. A custom wildcard configuration with credentials
reflects the request Origin and sends Vary: Origin, but explicit origins are
recommended for production.
Logging remains disabled when no logger is configured. After calling
SetLogger or SetStdLogger, logging is enabled by default. Use
log.SetEnabled(false) when it needs to be disabled at runtime.
All middleware packages share a common logging interface.
By default, logging is disabled (discard). If you don't need logs, no logging setup is required:
// 什么都不需要做 — 日志默认关闭
import (
"github.com/jackman0925/gin-middleware/jwt"
"github.com/jackman0925/gin-middleware/cors"
)If you want to see logs:
import "github.com/jackman0925/gin-middleware/log"
// 标准库日志
log.SetStdLogger(log.LevelInfo)
// 配置 logger 后默认开启;可以随时关闭并重新开启
log.SetEnabled(false)
log.SetEnabled(true)
// 接入 glog(github.com/jackman0925/glog)— 只需 4 行
type glogAdapter struct{}
func (glogAdapter) Errorf(f string, v ...any) { glog.Errorf(f, v...) }
func (glogAdapter) Warnf(f string, v ...any) { glog.Warnf(f, v...) }
func (glogAdapter) Infof(f string, v ...any) { glog.Infof(f, v...) }
func (glogAdapter) Debugf(f string, v ...any) { glog.Debugf(f, v...) }
log.SetLogger(glogAdapter{}, log.LevelDebug)Calling log.SetLogger(nil, level) removes the configured logger and disables
logging. log.IsEnabled() reports whether a logger is both configured and
enabled. Runtime logger configuration and toggling are concurrency-safe.
Use the request package when JSON fields need defaults before validation:
import "github.com/jackman0925/gin-middleware/request"
type CreateUserRequest struct {
Name string `json:"name" binding:"required"`
Page int `json:"page" default:"1" binding:"required,min=1"`
Enabled bool `json:"enabled" default:"true"`
}
func createUser(c *gin.Context) {
var req CreateUserRequest
if err := request.BindJSON(c, &req); err != nil {
response.Fail(c, http.StatusBadRequest, err)
return
}
// Missing page and enabled become 1 and true before validation.
}BindJSON decodes the body, applies default:"..." tags, then invokes Gin's
validator. It therefore supports default together with binding:"required".
Explicit JSON values such as false, 0, and "" are preserved rather than
overwritten. Supported scalar defaults are string, bool, integer, unsigned
integer, float, time.Duration (for example default:"5s"), and RFC3339
time.Time; slices, maps, arrays, and structs use a JSON literal in the tag.
The request body is cached in Gin's BodyBytesKey, so it remains available to
ShouldBindBodyWithJSON. SetReqDefaults is available for non-JSON values; it
treats zero values as unset, so prefer BindJSON for JSON requests.
import "github.com/jackman0925/gin-middleware/jwt"
// Create JWT middleware with default config (HS256, 72h expiration)
j := jwt.New("your-32-char+ secret key here!!")
// Generate a token
token, err := j.GenerateTokenWithUsername("admin", map[string]interface{}{
"adminID": 1,
"role": "admin",
})
// Use as Gin middleware
r := gin.Default()
admin := r.Group("/admin")
admin.Use(j.Middleware())
{
admin.GET("/dashboard", func(c *gin.Context) {
username, _ := jwt.UsernameFromContext(c)
c.JSON(200, gin.H{"username": username})
})
}Custom configuration:
import jwtlib "github.com/golang-jwt/jwt/v5"
j := jwt.NewWithConfig(jwt.Config{
Secret: "your-secret-key-must-have-32-bytes",
TokenHeaderName: "Authorization",
TokenPrefix: "Bearer",
Expiration: time.Hour * 24,
SigningMethod: jwtlib.SigningMethodHS256,
Issuer: "my-service", // optional
Audience: "my-client", // optional
Leeway: 30 * time.Second,
})JWT validation accepts only the configured HMAC algorithm and requires an
exp claim by default. Set AllowMissingExpiration only when compatibility
with non-expiring legacy tokens is required. Claims are stored under a
package-owned Gin context key; use jwt.ClaimsFromContext and
jwt.UsernameFromContext instead of reading arbitrary context keys.
import "github.com/jackman0925/gin-middleware/cors"
r := gin.Default()
// Allow specific origins
r.Use(cors.New([]string{"https://example.com", "https://app.example.com"}))
// Or allow all (development)
r.Use(cors.AllowAll())Custom configuration:
r.Use(cors.NewWithConfig(cors.Config{
AllowedOrigins: []string{"https://example.com"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
AllowCredentials: true,
MaxAge: 86400,
}))cors.New([]string{"*"}) disables credentials because browsers do not accept
wildcard origins with credentialed requests. If a custom configuration combines
AllowedOrigins: []string{"*"} with AllowCredentials: true, the middleware
reflects the concrete request origin and adds Vary: Origin.
import "github.com/jackman0925/gin-middleware/errorhandler"
r.Use(errorhandler.ErrorHandler())Private errors receive a generic internal server error response while their
details remain available to the configured logger. Mark an error with
gin.ErrorTypePublic only when its text is safe for clients, or use
ErrorHandlerWithMapper to map application errors to statuses and messages.
import "github.com/jackman0925/gin-middleware/response"
r.GET("/api/users", func(c *gin.Context) {
users := getUsers()
response.Success(c, users)
// Optional application code, independent from HTTP 200
response.SuccessWithCode(c, 1001, "users loaded", users)
})
r.GET("/api/products", func(c *gin.Context) {
products := getProducts()
response.SuccessPagination(c, products, page, pageSize, total)
})
r.GET("/api/item/:id", func(c *gin.Context) {
item, err := getItem(id)
if err != nil {
response.Fail(c, http.StatusNotFound, err)
return
}
response.Success(c, item)
})Use FailWithCode and SuccessPaginationWithCode when the API contract uses
application-specific codes rather than HTTP statuses as response codes.
MIT