初始提交:一场soul的创业实验-永平 网站与小程序
Made-with: Cursor
This commit is contained in:
44
soul-api/internal/middleware/admin_auth.go
Normal file
44
soul-api/internal/middleware/admin_auth.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"soul-api/internal/auth"
|
||||
"soul-api/internal/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const adminClaimsKey = "admin_claims"
|
||||
|
||||
// AdminAuth 管理端鉴权:校验 JWT(Authorization: Bearer 或 Cookie admin_session),未登录返回 401;通过则设置 admin_claims 到 context
|
||||
func AdminAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
cfg := config.Get()
|
||||
if cfg == nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
token := auth.GetAdminJWTFromRequest(c.Request)
|
||||
claims, ok := auth.ParseAdminJWT(token, cfg.AdminSessionSecret)
|
||||
if !ok {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"success": false, "error": "未授权访问,请先登录"})
|
||||
return
|
||||
}
|
||||
c.Set(adminClaimsKey, claims)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// GetAdminClaims 从 context 获取 admin claims(需在 AdminAuth 之后调用)
|
||||
func GetAdminClaims(c *gin.Context) *auth.AdminClaims {
|
||||
v, ok := c.Get(adminClaimsKey)
|
||||
if !ok || v == nil {
|
||||
return nil
|
||||
}
|
||||
claims, ok := v.(*auth.AdminClaims)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return claims
|
||||
}
|
||||
65
soul-api/internal/middleware/ratelimit.go
Normal file
65
soul-api/internal/middleware/ratelimit.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RateLimiter 按 IP 的限流器
|
||||
type RateLimiter struct {
|
||||
mu sync.Mutex
|
||||
clients map[string]*rate.Limiter
|
||||
r rate.Limit
|
||||
b int
|
||||
}
|
||||
|
||||
// NewRateLimiter 创建限流中间件,r 每秒请求数,b 突发容量
|
||||
func NewRateLimiter(r rate.Limit, b int) *RateLimiter {
|
||||
return &RateLimiter{
|
||||
clients: make(map[string]*rate.Limiter),
|
||||
r: r,
|
||||
b: b,
|
||||
}
|
||||
}
|
||||
|
||||
// getLimiter 获取或创建该 key 的 limiter
|
||||
func (rl *RateLimiter) getLimiter(key string) *rate.Limiter {
|
||||
rl.mu.Lock()
|
||||
defer rl.mu.Unlock()
|
||||
if lim, ok := rl.clients[key]; ok {
|
||||
return lim
|
||||
}
|
||||
lim := rate.NewLimiter(rl.r, rl.b)
|
||||
rl.clients[key] = lim
|
||||
return lim
|
||||
}
|
||||
|
||||
// Middleware 返回 Gin 限流中间件(按客户端 IP)
|
||||
func (rl *RateLimiter) Middleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
key := c.ClientIP()
|
||||
lim := rl.getLimiter(key)
|
||||
if !lim.Allow() {
|
||||
c.AbortWithStatus(http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup 定期清理过期 limiter(可选,避免 map 无限增长)
|
||||
func (rl *RateLimiter) Cleanup(interval time.Duration) {
|
||||
ticker := time.NewTicker(interval)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
rl.mu.Lock()
|
||||
rl.clients = make(map[string]*rate.Limiter)
|
||||
rl.mu.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
25
soul-api/internal/middleware/secure.go
Normal file
25
soul-api/internal/middleware/secure.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/unrolled/secure"
|
||||
)
|
||||
|
||||
// Secure 安全响应头中间件
|
||||
func Secure() gin.HandlerFunc {
|
||||
s := secure.New(secure.Options{
|
||||
FrameDeny: true,
|
||||
ContentTypeNosniff: true,
|
||||
BrowserXssFilter: true,
|
||||
ContentSecurityPolicy: "frame-ancestors 'none'",
|
||||
ReferrerPolicy: "no-referrer",
|
||||
})
|
||||
return func(c *gin.Context) {
|
||||
err := s.Process(c.Writer, c.Request)
|
||||
if err != nil {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user