diff --git a/api/v1/system/auth.go b/api/v1/system/auth.go index 9469281..a972d00 100644 --- a/api/v1/system/auth.go +++ b/api/v1/system/auth.go @@ -6,7 +6,7 @@ import ( "sundynix-go/model/system" systemReq "sundynix-go/model/system/request" systemRes "sundynix-go/model/system/response" - "sundynix-go/utils" + "sundynix-go/utils/jwt" "github.com/gin-gonic/gin" "github.com/mojocn/base64Captcha" @@ -41,7 +41,17 @@ func (a *AuthApi) Login(c *gin.Context) { // 登出 func (a *AuthApi) Logout(c *gin.Context) { - utils.ClearToken(c) + token := jwt.GetToken(c) + userId := jwt.GetUserId(c) + err := jwtService.PutBlacklist(userId, token) + if err != nil { + global.Logger.Error("登出失败!", zap.Error(err)) + response.FailWithMsg("登出失败", c) + return + } + jwt.ClearToken(c) + response.OkWithMsg("登出成功", c) + } // Captcha api 生成验证码 @@ -69,7 +79,7 @@ func (u *AuthApi) Captcha(c *gin.Context) { } func (a *AuthApi) GetToken(c *gin.Context, user system.User) { - token, claims, err := utils.GetLoginToken(&user) + token, claims, err := jwt.GetLoginToken(&user) if err != nil { global.Logger.Error("GetToken err", zap.Error(err)) response.FailWithMsg("GetToken err", c) diff --git a/api/v1/system/enter.go b/api/v1/system/enter.go index 23d1fa7..0ad22fb 100644 --- a/api/v1/system/enter.go +++ b/api/v1/system/enter.go @@ -11,6 +11,7 @@ type ApiGroup struct { } var ( + jwtService = service.ServiceGroupApp.SystemServiceGroup.JwtService userService = service.ServiceGroupApp.SystemServiceGroup.UserService clientService = service.ServiceGroupApp.SystemServiceGroup.ClientService roleService = service.ServiceGroupApp.SystemServiceGroup.RoleService diff --git a/api/v1/system/sys_menu.go b/api/v1/system/sys_menu.go index 93ab381..6b4afee 100644 --- a/api/v1/system/sys_menu.go +++ b/api/v1/system/sys_menu.go @@ -5,7 +5,7 @@ import ( "sundynix-go/model/commom/response" "sundynix-go/model/system" systemReq "sundynix-go/model/system/request" - "sundynix-go/utils" + "sundynix-go/utils/jwt" "github.com/gin-gonic/gin" "go.uber.org/zap" @@ -89,7 +89,7 @@ func (m *MenuApi) GetUserMenuTree(c *gin.Context) { } func (m *MenuApi) Route(c *gin.Context) { - userId := utils.GetUserId(c) + userId := jwt.GetUserId(c) routes, err := menuService.GetUserRoutes(userId) if err != nil { global.Logger.Error("获取用户菜单失败!", zap.Error(err)) diff --git a/middleware/auth.go b/middleware/auth.go index 793bc5a..0eff89b 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -2,34 +2,43 @@ package middleware import ( "errors" - "github.com/gin-gonic/gin" "sundynix-go/model/commom/response" - "sundynix-go/utils" + "sundynix-go/service" + "sundynix-go/utils/jwt" + + "github.com/gin-gonic/gin" ) +var jwtService = service.ServiceGroupApp.SystemServiceGroup.JwtService + // AuthMiddleware 验证token有效性 func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { - token := utils.GetToken(c) + token := jwt.GetToken(c) if token == "" { response.NoAuth("未登录或非法访问", c) c.Abort() return } - //todo 黑名单处理 + userId := jwt.GetUserId(c) + if jwtService.IsInBlacklist(userId, token) { + response.NoAuth("未登录或令牌失效", c) + c.Abort() + return + } - j := utils.NewJWT() + j := jwt.NewJWT() // 解析token信息 claims, err := j.ParseToken(token) if err != nil { - if errors.Is(err, utils.TokenExpired) { + if errors.Is(err, jwt.TokenExpired) { response.NoAuth("登录过期", c) - utils.ClearToken(c) + jwt.ClearToken(c) c.Abort() return } response.NoAuth(err.Error(), c) - utils.ClearToken(c) + jwt.ClearToken(c) c.Abort() return } diff --git a/router/system/auth_router.go b/router/system/auth_router.go index 2da4f61..86b701a 100644 --- a/router/system/auth_router.go +++ b/router/system/auth_router.go @@ -13,5 +13,6 @@ func (s *AuthRouter) InitAuthRouter(Router *gin.RouterGroup) { { loginRouter.POST("login", authApi.Login) loginRouter.GET("captcha", authApi.Captcha) + loginRouter.GET("logout", authApi.Logout) // 服务端不保存任何登录状态 退出实际是禁用了当前的jwt } } diff --git a/service/system/enter.go b/service/system/enter.go index 4474727..5643153 100644 --- a/service/system/enter.go +++ b/service/system/enter.go @@ -1,6 +1,7 @@ package system type ServiceGroup struct { + JwtService UserService ClientService RoleService diff --git a/service/system/sys_jwt.go b/service/system/sys_jwt.go new file mode 100644 index 0000000..ceb7508 --- /dev/null +++ b/service/system/sys_jwt.go @@ -0,0 +1,26 @@ +package system + +import ( + "context" + "sundynix-go/global" + "sundynix-go/utils" +) + +type JwtService struct{} + +var JwtServiceApp = new(JwtService) + +// 登出,禁用jwt +func (s JwtService) PutBlacklist(userId string, token string) (err error) { + expire, err := utils.ParseDuration(global.Config.JWT.ExpiresTime) + if err != nil { + return err + } + err = global.Redis.Set(context.Background(), userId, token, expire).Err() + return err +} + +func (s JwtService) IsInBlacklist(userId string, token string) bool { + val, err := global.Redis.Get(context.Background(), userId).Result() + return err == nil && val == token +} diff --git a/utils/claims.go b/utils/jwt/claims.go similarity index 64% rename from utils/claims.go rename to utils/jwt/claims.go index c847c1c..31a42bd 100644 --- a/utils/claims.go +++ b/utils/jwt/claims.go @@ -1,7 +1,8 @@ -package utils +package jwt import ( "net" + "strings" "sundynix-go/global" "sundynix-go/model/system" systemReq "sundynix-go/model/system/request" @@ -20,33 +21,7 @@ func GetLoginToken(user system.Login) (token string, claims systemReq.CustomClai return } -// SetToken 设置一个名为 "x-token" 的 Cookie,并根据请求的主机名和 IP 地址来设置 Cookie 的域。 -// -// 参数: -// - c: *gin.Context, Gin 框架的上下文对象,用于处理 HTTP 请求和响应。 -// - token: string, 要设置的 token 值。 -// - maxAge: int, Cookie 的最大生存时间(以秒为单位)。 -// -// 该函数首先从请求的主机名中提取出主机部分(去除端口号),然后判断该主机名是否为 IP 地址。 -// 如果是 IP 地址,则设置 Cookie 的域为空;否则,将 Cookie 的域设置为提取出的主机名。 -func SetToken(c *gin.Context, token string, maxAge int) { - // 从请求的主机名中提取主机部分,忽略端口号 - host, _, err := net.SplitHostPort(c.Request.Host) - if err != nil { - host = c.Request.Host - } - - // 判断主机名是否为 IP 地址,并根据结果设置 Cookie 的域 - if net.ParseIP(host) != nil { - c.SetCookie("x-token", token, maxAge, "/", "", false, false) - } else { - c.SetCookie("x-token", token, maxAge, "/", host, false, false) - } -} - -// GetToken 从请求头或Cookie中获取JWT token,并确保其有效性。 -// 如果请求头中没有Authorization字段,则尝试从Cookie中获取token,并解析验证其有效性。 -// 如果token有效,则将其重新写入Cookie,并设置过期时间。 +// GetToken 从请求头中获取JWT token,并确保其有效性 // // 参数: // - c: *gin.Context, Gin框架的上下文对象,用于获取请求信息和设置响应。 @@ -56,24 +31,10 @@ func SetToken(c *gin.Context, token string, maxAge int) { func GetToken(c *gin.Context) string { // 从请求头中获取Authorization字段的值 token := c.Request.Header.Get("Authorization") - - //// 如果请求头中没有Authorization字段,则尝试从Cookie中获取token - //if token == "" { - // j := NewJWT() - // token, _ = c.Cookie("x-token") - // - // // 解析并验证token的有效性 - // claims, err := j.ParseToken(token) - // if err != nil { - // // 如果解析失败,记录错误日志并返回当前token - // global.Logger.Error("重新写入cookie token失败,未能成功解析token,请检查请求头是否存在x-token且claims是否为规定结构") - // return token - // } - // - // // 如果token有效,则将其重新写入Cookie,并设置过期时间 - // SetToken(c, token, int((claims.ExpiresAt.Unix()-time.Now().Unix())/60)) - //} - + prefix := strings.HasPrefix(token, "Bearer ") + if prefix { + token = strings.TrimPrefix(token, "Bearer ") + } // 返回获取到的token return token } @@ -85,9 +46,9 @@ func ClearToken(c *gin.Context) { host = c.Request.Host } if net.ParseIP(host) != nil { - c.SetCookie("x-token", "", -1, "/", "", false, false) + c.SetCookie("sundynix-token", "", -1, "/", "", false, false) } else { - c.SetCookie("x-token", "", -1, "/", host, false, false) + c.SetCookie("sundynix-token", "", -1, "/", host, false, false) } } diff --git a/utils/jwt.go b/utils/jwt/jwt.go similarity index 94% rename from utils/jwt.go rename to utils/jwt/jwt.go index f3787df..62c2cbb 100644 --- a/utils/jwt.go +++ b/utils/jwt/jwt.go @@ -1,9 +1,10 @@ -package utils +package jwt import ( "errors" "sundynix-go/global" "sundynix-go/model/system/request" + "sundynix-go/utils" "time" "github.com/golang-jwt/jwt/v5" @@ -31,8 +32,8 @@ func NewJWT() *JWT { // CreateClaims 创建Claims func (j *JWT) CreateClaims(baseClaims request.BaseClaims) request.CustomClaims { - bf, _ := ParseDuration(global.Config.JWT.BufferTime) - ep, _ := ParseDuration(global.Config.JWT.ExpiresTime) + bf, _ := utils.ParseDuration(global.Config.JWT.BufferTime) + ep, _ := utils.ParseDuration(global.Config.JWT.ExpiresTime) claims := request.CustomClaims{ BaseClaims: baseClaims, BufferTime: int64(bf / time.Second), // 缓冲时间1天 缓冲时间内会获得新的token刷新令牌 此时一个用户会存在两个有效令牌 但是前端只留一个 另一个会丢失