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