You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/internal/api/middleware/ratelimit.go

112 lines
2.5 KiB
Go

2 weeks ago
package middleware
import (
"fmt"
"net/http"
"time"
"github.com/veypi/vbase/internal/cache"
"github.com/veypi/vigo"
)
// RateLimiter 限流中间件
func RateLimiter(maxRequests int, window time.Duration) func(*vigo.X) (any, error) {
return func(x *vigo.X) (any, error) {
if !cache.IsEnabled() {
x.Next()
return nil, nil
}
// 使用IP+路径作为标识
identifier := x.GetRemoteIP()
path := x.Request.URL.Path
count, err := cache.IncrRateLimit(identifier, path, window)
if err != nil {
x.Next() // 缓存失败时放行
return nil, nil
}
if count > int64(maxRequests) {
x.ResponseWriter().Header().Set("Retry-After", fmt.Sprintf("%d", int(window.Seconds())))
return nil, vigo.NewError("rate limit exceeded").WithCode(http.StatusTooManyRequests)
}
x.Next()
return nil, nil
}
}
// RateLimiterByUser 基于用户的限流
func RateLimiterByUser(maxRequests int, window time.Duration) func(*vigo.X) (any, error) {
return func(x *vigo.X) (any, error) {
if !cache.IsEnabled() {
x.Next()
return nil, nil
}
userID := CurrentUser(x)
if userID == "" {
// 未登录用户使用IP限流
_, err := RateLimiter(maxRequests, window)(x)
return nil, err
}
path := x.Request.URL.Path
count, err := cache.IncrRateLimit("user:"+userID, path, window)
if err != nil {
x.Next()
return nil, nil
}
if count > int64(maxRequests) {
x.ResponseWriter().Header().Set("Retry-After", fmt.Sprintf("%d", int(window.Seconds())))
return nil, vigo.NewError("rate limit exceeded").WithCode(http.StatusTooManyRequests)
}
x.Next()
return nil, nil
}
}
// LoginRateLimit 登录限流(更严格)
func LoginRateLimit() func(*vigo.X) (any, error) {
return func(x *vigo.X) (any, error) {
if !cache.IsEnabled() {
x.Next()
return nil, nil
}
identifier := x.GetRemoteIP()
key := "login_attempt:" + identifier
count, _ := cache.Incr(key)
if count == 1 {
cache.Expire(key, 15*time.Minute)
}
// 5分钟内超过5次尝试需要验证码
if count >= 5 {
x.Set("require_captcha", true)
}
// 超过10次直接拒绝
if count >= 10 {
return nil, vigo.NewError("too many login attempts, please try again later").WithCode(http.StatusTooManyRequests)
}
x.Next()
return nil, nil
}
}
// ResetLoginAttempts 重置登录尝试次数
func ResetLoginAttempts(x *vigo.X) {
if !cache.IsEnabled() {
return
}
identifier := x.GetRemoteIP()
key := "login_attempt:" + identifier
cache.Delete(key)
}