go jwt和redis实现用户登录注册以及查询

本项目基于 Gin + GORM + MySQL + Redis,实现了一个简单的用户登录注册系统,核心功能包括 JWT 鉴权与 Redis 用户缓存。用户登录成功后签发 JWT,访问受保护接口时通过中间件校验 Token,有效提升接口安全性。用户资料优先从 Redis 获取,未命中则回源数据库并写入缓存,兼顾性能与一致性,适合作为入门级全栈身份认证系统的学习案例。

今天来使用go中的jwt来完成用户的登录注册,并使用redis缓存用户信息的功能

项目地址:

背景与目标

  • 本项目用 Gin + GORM + MySQL + Redis 实现一套“JWT 认证 + 用户资料缓存”的最小闭环:
    • 注册/登录获取 JWT
    • 中间件校验 JWT 保护接口
    • 访问资料时优先读 Redis 缓存,未命中则查询数据库 并回填缓存

配置加载

  • 通过环境变量覆盖默认值(JWT 密钥、MySQL、Redis 等):

数据库与 Redis 初始化

  • 初始化 MySQL 连接并记录日志;初始化 Redis 客户端并 Ping 测试连接。
go
func InitRedis() {
	RedisClient = redis.NewClient(&redis.Options{
		Addr:     fmt.Sprintf("%s:%s", config.AppConfig.RedisHost, config.AppConfig.RedisPort),
		Password: config.AppConfig.RedisPassword,
		DB:       config.AppConfig.RedisDB,
	})

	// 测试Redis连接
	_, err := RedisClient.Ping(context.Background()).Result()
	if err != nil {
		log.Fatal("Redis连接失败:", err)
	}

	log.Println("Redis连接成功")
}

模型与密码加密

  • User 模型唯一索引用户名/邮箱,注册时对密码进行 bcrypt 加密;登录比对哈希。
go
// 密码加密
func (u *User) HashPassword() error {
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	u.Password = string(hashedPassword)
	return nil
}

// 验证密码
func (u *User) CheckPassword(password string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
	return err == nil
}

JWT:签发与校验

  • 签发时将用户 ID/Username 写入自定义 Claims,过期时间 24 小时,HS256 对称签名;校验时解出 Claims。
go
type Claims struct {
	UserID   uint   `json:"user_id"`
	Username string `json:"username"`
	jwt.RegisteredClaims
}	

// 生成JWT Token
func GenerateToken(userID uint, username string) (string, error) {
	claims := Claims{
		UserID:   userID,
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // 24小时过期
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			NotBefore: jwt.NewNumericDate(time.Now()),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(config.AppConfig.JWTSecret))
}
go
// 验证JWT Token
func ValidateToken(tokenString string) (*Claims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(config.AppConfig.JWTSecret), nil
	})

	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*Claims); ok && token.Valid {
		return claims, nil
	}

	return nil, errors.New("无效的token")
}

鉴权中间件:保护接口

  • 从 Authorization: Bearer <token> 提取 token,校验后把 user_id/username 放入上下文,用于后续处理。

路由与模块装配

  • 公开 API:注册、登录;保护 API:获取资料、登出、更新资料;并挂载静态演示页。
go
// 公开路由
public := r.Group("/api")
{
	public.POST("/register", handlers.Register)
	public.POST("/login", handlers.Login)
}

// 需要认证的路由
protected := r.Group("/api")
protected.Use(middleware.AuthMiddleware())
{
	protected.GET("/profile", handlers.GetProfile)
	protected.POST("/logout", handlers.Logout)
	protected.PUT("/profile", handlers.UpdateProfile)
}

登录与签发 Token

  • 登录成功后返回 token 与用户信息:

使用 Redis 做用户资料缓存

  • 访问 GET /api/profile 时:
    • 先读 Redis userId:<id>,命中则直接返回;
    • 未命中则查 DB 并以 TTL=1 分钟回填缓存。

时序与数据流

  • 简图说明(登录与访问资料):
Mermaid
sequenceDiagram
  autonumber
  participant UI as 浏览器
  participant API as Gin API
  participant DB as MySQL
  participant R as Redis

  UI->>API: POST /api/login (username,password)
  API->>DB: 查询用户 & 校验密码
  DB-->>API: 用户信息
  API-->>UI: 200 { token, user }

  UI->>API: GET /api/profile (Authorization: Bearer token)
  API->>API: 中间件校验 JWT
  API->>R: GET userId:<id>
  alt 命中
    R-->>API: 用户缓存
    API-->>UI: 200 { user }
  else 未命中
    API->>DB: SELECT user by id
    DB-->>API: 用户记录
    API->>R: SET userId:<id> TTL=60s
    API-->>UI: 200 { user }
  end

今天的教程就到这里,这只是一个demo,大概熟悉一下redis如何使用

Go Context 原理与使用实践
Go Panic 与 Recover 异常处理