文档
Go JWT 认证中间件
目标
用 golang-jwt/jwt 实现 Token 签发和验证,作为 Gin 中间件保护私有 API。
完整代码
package main
import (
"errors"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
var jwtSecret = []byte("your-secret-key-change-in-production")
type Claims struct {
UserID int `json:"userId"`
Username string `json:"username"`
Role string `json:"role"`
jwt.RegisteredClaims
}
// GenerateToken 签发 Token
func GenerateToken(userID int, username, role string) (string, error) {
claims := Claims{
UserID: userID,
Username: username,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "my-bishe",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
// ParseToken 解析并验证 Token
func ParseToken(tokenStr string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (any, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("无效签名算法")
}
return jwtSecret, nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, errors.New("无效 Token")
}
return claims, nil
}
// AuthMiddleware JWT 认证中间件
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenStr := c.GetHeader("Authorization")
if len(tokenStr) < 8 || tokenStr[:7] != "Bearer " {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未提供 Token"})
return
}
claims, err := ParseToken(tokenStr[7:])
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Token 无效或已过期"})
return
}
c.Set("userId", claims.UserID)
c.Set("username", claims.Username)
c.Set("role", claims.Role)
c.Next()
}
}
// RoleMiddleware 角色权限中间件(工厂函数)
func RoleMiddleware(roles ...string) gin.HandlerFunc {
return func(c *gin.Context) {
role, _ := c.Get("role")
for _, r := range roles {
if role == r {
c.Next()
return
}
}
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "无权访问"})
}
}
func main() {
r := gin.Default()
// 公开接口:登录获取 Token
r.POST("/login", func(c *gin.Context) {
var req struct {
Username string `json:"username"`
Password string `json:"password"`
}
c.ShouldBindJSON(&req)
// 模拟验证(实际应查数据库)
if req.Username == "admin" && req.Password == "123456" {
token, _ := GenerateToken(1, req.Username, "admin")
c.JSON(200, gin.H{"token": token, "expiresIn": "7天"})
} else {
c.JSON(401, gin.H{"error": "账号或密码错误"})
}
})
// 受保护路由组
auth := r.Group("/api").Use(AuthMiddleware())
{
auth.GET("/profile", func(c *gin.Context) {
c.JSON(200, gin.H{
"userId": c.GetInt("userId"),
"username": c.GetString("username"),
"role": c.GetString("role"),
})
})
// 仅管理员可访问
admin := auth.Group("/admin").Use(RoleMiddleware("admin"))
{
admin.GET("/dashboard", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "管理员面板"})
})
}
}
r.Run(":8080")
}
测试
# 登录获取 Token
curl -X POST http://localhost:8080/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
# 使用 Token 访问受保护接口
curl http://localhost:8080/api/profile \
-H "Authorization: Bearer <TOKEN>"
# 非管理员访问 admin 接口 → 403
关键要点
- HS256 对称加密,简单够用;生产环境用 RS256(非对称)
- Token 存在客户端,服务端不存储状态
- 过期时间设置 7 天,可选实现 Refresh Token 续期