fix: 修复权限边界与安全问题
- JWT 密钥改为环境变量/配置文件/随机生成注入,移除硬编码 - AuthMiddleware 用数据库 role 覆盖 claims,降级/禁用即时生效 - 改 role/status 时主动失效目标用户会话 - /ws 支持 JWT 认证(?token=),兼容密码认证,CheckOrigin 改同源校验 - 默认管理员改用随机密码,stdout 与文件双交付 - 登录与 WS 密码认证加限流(5次/分钟) - role 字段白名单校验(仅 admin/user) - socket/目录权限可配置(sock_mode/sock_group/sock_dir_mode),默认兼容现状 - 配置文件权限收紧 0644→0600,目录 0755→0700 - 静态文件改用 http.Dir.Open + NoRoute,移除手动路径拼接 - 隧道加 SetReadLimit(1MB) 与单用户连接数上限(3) - README 补充安全配置与生产部署说明
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type RateLimiter struct {
|
||||
mu sync.Mutex
|
||||
attempts map[string][]time.Time
|
||||
limit int
|
||||
window time.Duration
|
||||
}
|
||||
|
||||
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
|
||||
rl := &RateLimiter{
|
||||
attempts: make(map[string][]time.Time),
|
||||
limit: limit,
|
||||
window: window,
|
||||
}
|
||||
go rl.cleanup()
|
||||
return rl
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) Allow(key string) bool {
|
||||
rl.mu.Lock()
|
||||
defer rl.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
cutoff := now.Add(-rl.window)
|
||||
|
||||
var recent []time.Time
|
||||
for _, t := range rl.attempts[key] {
|
||||
if t.After(cutoff) {
|
||||
recent = append(recent, t)
|
||||
}
|
||||
}
|
||||
|
||||
if len(recent) >= rl.limit {
|
||||
rl.attempts[key] = recent
|
||||
return false
|
||||
}
|
||||
|
||||
recent = append(recent, now)
|
||||
rl.attempts[key] = recent
|
||||
return true
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) cleanup() {
|
||||
ticker := time.NewTicker(rl.window)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
rl.mu.Lock()
|
||||
cutoff := time.Now().Add(-rl.window)
|
||||
for key, times := range rl.attempts {
|
||||
var recent []time.Time
|
||||
for _, t := range times {
|
||||
if t.After(cutoff) {
|
||||
recent = append(recent, t)
|
||||
}
|
||||
}
|
||||
if len(recent) == 0 {
|
||||
delete(rl.attempts, key)
|
||||
} else {
|
||||
rl.attempts[key] = recent
|
||||
}
|
||||
}
|
||||
rl.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func LoginRateLimit() gin.HandlerFunc {
|
||||
limiter := NewRateLimiter(5, time.Minute)
|
||||
return func(c *gin.Context) {
|
||||
key := c.ClientIP()
|
||||
if !limiter.Allow(key) {
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{"error": "请求过于频繁,请稍后再试"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user