From 7fdea90deddad0f4de48d4097e8769d1d9aa781b Mon Sep 17 00:00:00 2001 From: Victor_Jay Date: Tue, 2 Jun 2026 19:03:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=8F=E6=84=9F=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0IP=E7=BB=B4=E5=BA=A6=E9=99=90=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RateLimiter 新增 AllowSensitive 方法(1分钟5次,超限封禁5分钟) - 新增 SensitiveRateLimit Gin 中间件 - 改密(PUT /api/settings/password)增加限流保护 - 注销(POST /api/settings/delete-account)增加限流保护 - 签到(POST /api/level/checkin)增加限流保护 --- internal/middleware/rate.go | 24 ++++++++++++++++++++++++ internal/middleware/ratelimit.go | 5 +++++ internal/middleware/ratelimit_core.go | 5 +++++ internal/router/api.go | 6 +++--- internal/router/deps_core.go | 1 + internal/router/deps_extra.go | 4 ++-- 6 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 internal/middleware/rate.go diff --git a/internal/middleware/rate.go b/internal/middleware/rate.go new file mode 100644 index 0000000..255dfb2 --- /dev/null +++ b/internal/middleware/rate.go @@ -0,0 +1,24 @@ +package middleware + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// SensitiveRateLimit 敏感操作(改密/注销/签到)IP 维度限流中间件。 +// 1 分钟最多 5 次请求,超限后封禁 5 分钟。 +func SensitiveRateLimit(rl *RateLimiter) gin.HandlerFunc { + return func(c *gin.Context) { + result := rl.AllowSensitive(c.ClientIP()) + if result.Blocked { + c.JSON(http.StatusTooManyRequests, gin.H{ + "success": false, + "error": result.Message, + }) + c.Abort() + return + } + c.Next() + } +} diff --git a/internal/middleware/ratelimit.go b/internal/middleware/ratelimit.go index 941a9c3..cc7e7ab 100644 --- a/internal/middleware/ratelimit.go +++ b/internal/middleware/ratelimit.go @@ -32,3 +32,8 @@ func (rl *RateLimiter) AllowAccount(email string) RateLimitResult { func (rl *RateLimiter) AllowIP(ip string) RateLimitResult { return rl.try(rl.ipFailures, ip, ipWindow, ipBlockDur, ipMaxFails, false) } + +// AllowSensitive 敏感操作限流(改密/注销/签到),基于 IP,1 分钟最多 5 次,超限封禁 5 分钟。 +func (rl *RateLimiter) AllowSensitive(ip string) RateLimitResult { + return rl.try(rl.ipFailures, "sensitive:"+ip, sensitiveWindow, sensitiveBlockDur, sensitiveMaxRequests, false) +} diff --git a/internal/middleware/ratelimit_core.go b/internal/middleware/ratelimit_core.go index 94b92d7..d0adf0d 100644 --- a/internal/middleware/ratelimit_core.go +++ b/internal/middleware/ratelimit_core.go @@ -21,6 +21,11 @@ const ( ipBlockDur = 1 * time.Minute cleanupInterval = 2 * time.Minute maxEntries = 10000 + + // 敏感操作限流:改密/注销/签到 + sensitiveWindow = 1 * time.Minute + sensitiveMaxRequests = 5 + sensitiveBlockDur = 5 * time.Minute ) // windowState 单个维度的限流状态 diff --git a/internal/router/api.go b/internal/router/api.go index 291c307..c07a590 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -17,8 +17,8 @@ func setupAPIRoutes(r *gin.Engine, cfg *config.Config, d *dependencies) { { settingsAPI.PUT("/profile", d.settingsCtrl.UpdateProfile) settingsAPI.POST("/avatar", d.settingsCtrl.UploadAvatar) - settingsAPI.PUT("/password", d.settingsCtrl.ChangePassword) - settingsAPI.POST("/delete-account", d.settingsCtrl.DeleteAccount) + settingsAPI.PUT("/password", middleware.SensitiveRateLimit(d.rateLimiter), d.settingsCtrl.ChangePassword) + settingsAPI.POST("/delete-account", middleware.SensitiveRateLimit(d.rateLimiter), d.settingsCtrl.DeleteAccount) settingsAPI.GET("/audit-status", d.settingsCtrl.AuditStatus) // 登录管理 settingsAPI.GET("/sessions", d.settingsCtrl.ListSessions) @@ -47,7 +47,7 @@ func setupAPIRoutes(r *gin.Engine, cfg *config.Config, d *dependencies) { levelAPI.Use(d.authMdw.BannedWriteGuard()) levelAPI.Use(middleware.CSRF(cfg)) { - levelAPI.POST("/checkin", d.levelCtrl.CheckIn) + levelAPI.POST("/checkin", middleware.SensitiveRateLimit(d.rateLimiter), d.levelCtrl.CheckIn) levelAPI.GET("/info", d.levelCtrl.GetLevel) } diff --git a/internal/router/deps_core.go b/internal/router/deps_core.go index 945dba5..3ece49e 100644 --- a/internal/router/deps_core.go +++ b/internal/router/deps_core.go @@ -20,6 +20,7 @@ import ( type dependencies struct { authCtrl *controller.AuthController authMdw *middleware.AuthMiddleware + rateLimiter *middleware.RateLimiter maintenanceMdw *middleware.MaintenanceMiddleware settingsCtrl *controller.SettingsController messageCtrl *controller.MessageController diff --git a/internal/router/deps_extra.go b/internal/router/deps_extra.go index 110ed70..519ac8c 100644 --- a/internal/router/deps_extra.go +++ b/internal/router/deps_extra.go @@ -16,7 +16,7 @@ import ( ) func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSettings, redisClient *redis.Client) *dependencies { - userRepo, sessionMgr, authService, _, authCtrl, avatarSvc, authMdw := buildDepsCore(db, cfg, siteSettings, redisClient) + userRepo, sessionMgr, authService, rateLimiter, authCtrl, avatarSvc, authMdw := buildDepsCore(db, cfg, siteSettings, redisClient) auditRepo := repository.NewAuditRepo(db) notificationRepo := repository.NewNotificationRepo(db) @@ -107,7 +107,7 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting postService.WithHomePageInvalidator(func() { homeCache.Invalidate() }) return &dependencies{ - authCtrl: authCtrl, authMdw: authMdw, maintenanceMdw: maintenanceMdw, + authCtrl: authCtrl, authMdw: authMdw, rateLimiter: rateLimiter, maintenanceMdw: maintenanceMdw, settingsCtrl: settingsCtrl, messageCtrl: messageController, postCtrl: postCtrl, postService: postService, spaceCtrl: spaceCtrl, studioCtrl: studioCtrl,