JWT认证 #

一、JWT概述 #

1.1 什么是JWT #

JWT(JSON Web Token)是一种开放标准,用于在各方之间安全传输信息:

text
JWT = Header.Payload.Signature

示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

1.2 JWT组成 #

部分 说明
Header 算法和类型
Payload 用户数据
Signature 签名验证

1.3 JWT优势 #

优势 说明
无状态 服务器不需要存储Session
跨域 支持跨域认证
移动端 适合移动应用
性能 减少数据库查询

二、安装JWT库 #

2.1 安装 #

bash
go get github.com/golang-jwt/jwt/v5

2.2 导入 #

go
import "github.com/golang-jwt/jwt/v5"

三、JWT实现 #

3.1 定义Claims #

go
type Claims struct {
    UserID uint   `json:"userId"`
    Role   string `json:"role"`
    jwt.RegisteredClaims
}

3.2 生成Token #

go
var jwtSecret = []byte("your-secret-key")

func GenerateToken(userID uint, role string) (string, error) {
    claims := Claims{
        UserID: userID,
        Role:   role,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "gin-demo",
        },
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(jwtSecret)
}

3.3 解析Token #

go
func ParseToken(tokenString string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        return jwtSecret, nil
    })
    
    if err != nil {
        return nil, err
    }
    
    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }
    
    return nil, jwt.ErrSignatureInvalid
}

3.4 JWT中间件 #

go
func JWTMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        
        if authHeader == "" {
            c.AbortWithStatusJSON(401, gin.H{
                "code":    401,
                "message": "未提供认证令牌",
            })
            return
        }
        
        // 移除Bearer前缀
        parts := strings.SplitN(authHeader, " ", 2)
        if len(parts) != 2 || parts[0] != "Bearer" {
            c.AbortWithStatusJSON(401, gin.H{
                "code":    401,
                "message": "认证格式错误",
            })
            return
        }
        
        tokenString := parts[1]
        claims, err := ParseToken(tokenString)
        if err != nil {
            c.AbortWithStatusJSON(401, gin.H{
                "code":    401,
                "message": "无效的认证令牌",
            })
            return
        }
        
        c.Set("userId", claims.UserID)
        c.Set("role", claims.Role)
        
        c.Next()
    }
}

四、完整认证示例 #

4.1 用户模型 #

go
type User struct {
    ID       uint   `gorm:"primaryKey" json:"id"`
    Username string `json:"username"`
    Password string `json:"-"`
    Role     string `json:"role"`
}

4.2 登录接口 #

go
func LoginHandler(c *gin.Context) {
    var req struct {
        Username string `json:"username" binding:"required"`
        Password string `json:"password" binding:"required"`
    }
    
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 查找用户
    var user User
    if err := DB.Where("username = ?", req.Username).First(&user).Error; err != nil {
        c.JSON(401, gin.H{"error": "用户名或密码错误"})
        return
    }
    
    // 验证密码
    if !CheckPassword(req.Password, user.Password) {
        c.JSON(401, gin.H{"error": "用户名或密码错误"})
        return
    }
    
    // 生成Token
    token, err := GenerateToken(user.ID, user.Role)
    if err != nil {
        c.JSON(500, gin.H{"error": "生成Token失败"})
        return
    }
    
    c.JSON(200, gin.H{
        "token": token,
        "user": gin.H{
            "id":       user.ID,
            "username": user.Username,
            "role":     user.Role,
        },
    })
}

4.3 注册接口 #

go
func RegisterHandler(c *gin.Context) {
    var req struct {
        Username string `json:"username" binding:"required,min=3,max=20"`
        Password string `json:"password" binding:"required,min=6"`
    }
    
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 检查用户名是否存在
    var count int64
    DB.Model(&User{}).Where("username = ?", req.Username).Count(&count)
    if count > 0 {
        c.JSON(400, gin.H{"error": "用户名已存在"})
        return
    }
    
    // 创建用户
    user := User{
        Username: req.Username,
        Password: HashPassword(req.Password),
        Role:     "user",
    }
    
    if err := DB.Create(&user).Error; err != nil {
        c.JSON(500, gin.H{"error": "创建用户失败"})
        return
    }
    
    c.JSON(201, gin.H{
        "id":       user.ID,
        "username": user.Username,
    })
}

4.4 获取用户信息 #

go
func GetProfileHandler(c *gin.Context) {
    userId := c.GetUint("userId")
    
    var user User
    if err := DB.First(&user, userId).Error; err != nil {
        c.JSON(404, gin.H{"error": "用户不存在"})
        return
    }
    
    c.JSON(200, gin.H{
        "id":       user.ID,
        "username": user.Username,
        "role":     user.Role,
    })
}

4.5 路由配置 #

go
func main() {
    r := gin.Default()
    
    // 公开路由
    r.POST("/login", LoginHandler)
    r.POST("/register", RegisterHandler)
    
    // 需要认证的路由
    protected := r.Group("/api")
    protected.Use(JWTMiddleware())
    {
        protected.GET("/profile", GetProfileHandler)
        protected.PUT("/profile", UpdateProfileHandler)
    }
    
    // 管理员路由
    admin := r.Group("/admin")
    admin.Use(JWTMiddleware())
    admin.Use(RoleMiddleware("admin"))
    {
        admin.GET("/users", ListUsersHandler)
        admin.DELETE("/users/:id", DeleteUserHandler)
    }
    
    r.Run()
}

五、刷新Token #

5.1 刷新机制 #

go
func RefreshTokenHandler(c *gin.Context) {
    userId := c.GetUint("userId")
    role := c.GetString("role")
    
    // 生成新Token
    token, err := GenerateToken(userId, role)
    if err != nil {
        c.JSON(500, gin.H{"error": "生成Token失败"})
        return
    }
    
    c.JSON(200, gin.H{"token": token})
}

5.2 双Token机制 #

go
type TokenPair struct {
    AccessToken  string `json:"accessToken"`
    RefreshToken string `json:"refreshToken"`
}

func GenerateTokenPair(userID uint, role string) (*TokenPair, error) {
    // 生成访问Token(短期)
    accessToken, err := GenerateToken(userID, role, time.Hour)
    if err != nil {
        return nil, err
    }
    
    // 生成刷新Token(长期)
    refreshToken, err := GenerateToken(userID, role, time.Hour*24*7)
    if err != nil {
        return nil, err
    }
    
    return &TokenPair{
        AccessToken:  accessToken,
        RefreshToken: refreshToken,
    }, nil
}

六、密码处理 #

6.1 密码哈希 #

go
import "golang.org/x/crypto/bcrypt"

func HashPassword(password string) string {
    bytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    return string(bytes)
}

func CheckPassword(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

七、权限控制 #

7.1 角色中间件 #

go
func RoleMiddleware(roles ...string) gin.HandlerFunc {
    return func(c *gin.Context) {
        userRole := c.GetString("role")
        
        for _, role := range roles {
            if userRole == role {
                c.Next()
                return
            }
        }
        
        c.AbortWithStatusJSON(403, gin.H{
            "code":    403,
            "message": "权限不足",
        })
    }
}

7.2 权限检查 #

go
func CheckPermission(permission string) gin.HandlerFunc {
    return func(c *gin.Context) {
        userId := c.GetUint("userId")
        
        if !HasPermission(userId, permission) {
            c.AbortWithStatusJSON(403, gin.H{
                "code":    403,
                "message": "权限不足",
            })
            return
        }
        
        c.Next()
    }
}

八、安全最佳实践 #

8.1 密钥管理 #

go
// 从环境变量获取密钥
var jwtSecret = []byte(os.Getenv("JWT_SECRET"))

// 或从配置文件读取
type JWTConfig struct {
    Secret     string `yaml:"secret"`
    ExpireTime int    `yaml:"expire_time"`
}

8.2 Token黑名单 #

go
var tokenBlacklist = make(map[string]time.Time)
var blacklistMutex sync.RWMutex

func AddToBlacklist(token string) {
    blacklistMutex.Lock()
    defer blacklistMutex.Unlock()
    tokenBlacklist[token] = time.Now()
}

func IsBlacklisted(token string) bool {
    blacklistMutex.RLock()
    defer blacklistMutex.RUnlock()
    _, exists := tokenBlacklist[token]
    return exists
}

func JWTMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // ... 解析Token
        
        if IsBlacklisted(tokenString) {
            c.AbortWithStatusJSON(401, gin.H{
                "code":    401,
                "message": "Token已失效",
            })
            return
        }
        
        // ...
    }
}

8.3 安全配置 #

go
type SecurityConfig struct {
    JWTSecret       string
    TokenExpireTime time.Duration
    MaxLoginAttempts int
    LockoutDuration  time.Duration
}

func DefaultSecurityConfig() *SecurityConfig {
    return &SecurityConfig{
        JWTSecret:       os.Getenv("JWT_SECRET"),
        TokenExpireTime: time.Hour,
        MaxLoginAttempts: 5,
        LockoutDuration:  time.Minute * 15,
    }
}

九、总结 #

9.1 核心要点 #

要点 说明
Token生成 jwt.NewWithClaims
Token解析 jwt.ParseWithClaims
中间件 验证Token并设置上下文
密码处理 使用bcrypt

9.2 最佳实践 #

实践 说明
密钥安全 使用环境变量存储密钥
Token过期 设置合理的过期时间
HTTPS 生产环境必须使用HTTPS
黑名单 实现Token失效机制

9.3 下一步 #

现在你已经掌握了JWT认证,接下来让我们学习 CORS跨域,了解跨域处理!

最后更新:2026-03-28