goalng中使用Redis和JWT实现登录双重验证

发布于:2025-07-18 ⋅ 阅读:(25) ⋅ 点赞:(0)


前言

  • Redis建议只执行一些标记 短时间、轻量级的,并且Redis带有key过期的属性,执行一些登录失效、秒杀订单过期时间标记,Redis本不执行任何的业务逻辑

Redis缓存以及JWT双重验证登录

  1. 导入redis包
go get github.com/go-redis/redis/v8
  1. 初始化Redis 新建 utlis/redis.go
 //utlis/redis.go

package utils

import (
	"context"
	"docker-go-app/config"
	"log"

	"github.com/go-redis/redis/v8"
)

//实例化
var (
	RedisClient *redis.Client
	Ctx         = context.Background()
)

// 初始化Redis连接
func InitRedis() {
	RedisClient = redis.NewClient(&redis.Options{
		Addr:     config.GetRedisAddr(), // 你的docker-compose里Redis端口  config.GetRedisAddr()全局变量获取
		Password: "",                    // 没有密码就留空
		DB:       config.GetRedisDB(),   // 默认DB
	})
}

// 测试Redis连接
func TestRedis() {
	err := RedisClient.Ping(Ctx).Err()
	if err != nil {
		log.Fatalf("Redis连接失败: %v", err)
	}
	log.Println("Redis连接成功")
}

  1. main.go初始化方法
package main

import (
	"docker-go-app/config"
	"docker-go-app/middleware"
	"docker-go-app/model"
	"docker-go-app/routers"
	"docker-go-app/utils"
	"log"

	"github.com/gin-gonic/gin"
)


func main() {
	// 初始化配置 作用:读取配置文件(config/config.yaml)
	config.InitConfig()

	// 初始化Redis连接
	utils.InitRedis()
	utils.TestRedis() // 测试Redis连接

	// 创建路由 gin框架的路由管理
	r := gin.Default()

	// 路由分组 传入路由实例
	routers.InitRouter(r)

	// 启动并监听服务器
	r.Run(":8080")
}

  1. user_controller.go 控制器登录成功之后,redis 缓存token
func (uc *UserController) Login(c *gin.Context) {
	// 绑定请求函数
	var loginForm types.LoginRequest

	// 验证请求参数
	if err := c.ShouldBindJSON(&loginForm); err != nil {
		utils.ParamError(c, "手机号和密码不能为空")
		return
	}
	user, token, err := uc.UserService.Login(loginForm.Phone, loginForm.Password)
	if err != nil {
		// 根据错误类型 返回不同的错误信息
		if err.Error() == "用户不存在" {
			utils.Fail(c, utils.ERROR_AUTH, "用户不存在")
		} else if err.Error() == "密码错误" {
			utils.Fail(c, utils.ERROR_AUTH, "密码错误")
		} else {
			utils.Fail(c, utils.ERROR_AUTH, "登录失败")
		}
		return
	}

	// 将token存储到Redis中 24小时过期
	utils.RedisClient.Set(utils.Ctx, "token:"+token, user.ID, time.Hour*24)

	response := types.LoginResponse{
		Token: token,
		User: struct {
			ID        uint      `json:"id" example:"1"`
			CreatedAt time.Time `json:"created_at" example:"2025-07-13T18:00:00Z"`
			UpdatedAt time.Time `json:"updated_at" example:"2025-07-13T18:00:00Z"`
			Username  string    `json:"username" example:"张三"`
			Email     string    `json:"email" example:"zhangsan@example.com"`
			Phone     string    `json:"phone" example:"13800138000"`
			Status    string    `json:"status" example:"1"`
			RealName  string    `json:"real_name" example:"张三"`
		}{
			ID:        user.ID,
			CreatedAt: user.CreatedAt,
			UpdatedAt: user.UpdatedAt,
			Username:  user.Username,
			Email:     user.Email,
			Phone:     user.Phone,
			Status:    user.Status,
			RealName:  user.RealName,
		},
	}
	utils.Success(c, response)
}

  1. 路由分组 userRouter.go 里 ,设置哪些路由是需要认证效验
package routers

import (
	"docker-go-app/controllers"
	"docker-go-app/middleware"
	"docker-go-app/services"

	"github.com/gin-gonic/gin"
)

// 初始化用户路由
func InitUserRouter(r *gin.Engine) { //模块路由 gin.RouterGroup

	// API版本分组
	apiV1 := r.Group("/api/v1")

	// 实例化服务和控制器
	userService := &services.UserService{}
	userController := &controllers.UserController{
		UserService: userService,
	}

	// 公开接口组
	public := apiV1.Group("/public")
	{
		public.POST("/login", userController.Login)
		public.POST("/register", userController.Register)
	}

	// 需要认证接口组
	protected := apiV1.Group("/user")
	protected.Use(middleware.Auth()) // 添加认证中间件
	{
		// 后续需要认证的接口
		// protected.POST("/logout", userController.Logout)
	}

}

  1. middleware/auth.go 在中间件里写入认证效验,满足则继续执行不满足则停止
// middleware/auth.go
package middleware

import (
	"docker-go-app/utils"
	"strings"

	"github.com/gin-gonic/gin"
)

// Auth 认证中间件
func Auth() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 获取Authorization头
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			utils.AuthError(c) // 认证失败
			c.Abort()          // 终止请求
			return
		}

		// 检查Authorization格式
		parts := strings.SplitN(authHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == "Bearer") {
			utils.AuthError(c) // 认证失败
			c.Abort()          // 终止请求
			return
		}

		// 解析token
		tokenString := parts[1]

		//1. 先效验 检查Redis中是否存在token/是否过期 服务端效验 服务端可控制 单点登录 踢人下线
		// token := strings.TrimPrefix(authHeader, "Bearer ")
		_, err := utils.RedisClient.Get(utils.Ctx, "token:"+tokenString).Result()
		if err != nil {
			utils.AuthError(c) // 认证失败
			c.Abort()          // 终止请求
			return
		}

		// 2. 再进行 JWT效验 jwt在客户端效验 生成token 就已经设置了过期时间,别人控制不了
		claims, err := utils.ParseToken(tokenString)
		if err != nil {
			// 根据错误类型设置不同的响应
			if strings.Contains(err.Error(), "expired") {
				utils.Fail(c, utils.ERROR_AUTH, "登录已过期,请重新登录") // 登录已过期
			} else {
				utils.AuthError(c) // 认证失败
			}
			c.Abort() // 终止请求
			return
		}

		// 将用户信息存储到上下文
		// 在这个请求的生命周期中,都是可以通过c.Get("userID")获取信息,处理相关逻辑
		// 请求结束,会自动释放
		c.Set("userID", claims.UserID)
		c.Set("phone", claims.Phone)

		c.Next()
	}
}


网站公告

今日签到

点亮在社区的每一天
去签到