Fix Services & Update the middleware && others
This commit is contained in:
@@ -1,84 +1,213 @@
|
||||
// Filename: internal/middleware/log_redaction.go
|
||||
// Filename: internal/middleware/logging.go
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const RedactedBodyKey = "redactedBody"
|
||||
const RedactedAuthHeaderKey = "redactedAuthHeader"
|
||||
const RedactedValue = `"[REDACTED]"`
|
||||
const (
|
||||
RedactedBodyKey = "redactedBody"
|
||||
RedactedAuthHeaderKey = "redactedAuthHeader"
|
||||
RedactedValue = `"[REDACTED]"`
|
||||
)
|
||||
|
||||
// 预编译正则表达式(全局变量,提升性能)
|
||||
var (
|
||||
// JSON 敏感字段脱敏
|
||||
jsonSensitiveKeys = regexp.MustCompile(`("(?i:api_key|apikey|token|password|secret|authorization|key|keys|auth)"\s*:\s*)"[^"]*"`)
|
||||
|
||||
// Bearer Token 脱敏
|
||||
bearerTokenPattern = regexp.MustCompile(`^(Bearer\s+)\S+$`)
|
||||
|
||||
// URL 中的 key 参数脱敏
|
||||
queryKeyPattern = regexp.MustCompile(`([?&](?i:key|token|apikey)=)[^&\s]+`)
|
||||
)
|
||||
|
||||
// RedactionMiddleware 请求数据脱敏中间件
|
||||
func RedactionMiddleware() gin.HandlerFunc {
|
||||
// Pre-compile regex for efficiency
|
||||
jsonKeyPattern := regexp.MustCompile(`("api_key"|"keys")\s*:\s*"[^"]*"`)
|
||||
bearerTokenPattern := regexp.MustCompile(`^(Bearer\s+)\S+$`)
|
||||
return func(c *gin.Context) {
|
||||
// --- 1. Redact Request Body ---
|
||||
if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "DELETE" {
|
||||
if bodyBytes, err := io.ReadAll(c.Request.Body); err == nil {
|
||||
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
|
||||
bodyString := string(bodyBytes)
|
||||
|
||||
redactedBody := jsonKeyPattern.ReplaceAllString(bodyString, `$1:`+RedactedValue)
|
||||
|
||||
c.Set(RedactedBodyKey, redactedBody)
|
||||
}
|
||||
}
|
||||
// --- 2. Redact Authorization Header ---
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader != "" {
|
||||
if bearerTokenPattern.MatchString(authHeader) {
|
||||
redactedHeader := bearerTokenPattern.ReplaceAllString(authHeader, `${1}[REDACTED]`)
|
||||
c.Set(RedactedAuthHeaderKey, redactedHeader)
|
||||
}
|
||||
// 1. 脱敏请求体
|
||||
if shouldRedactBody(c) {
|
||||
redactRequestBody(c)
|
||||
}
|
||||
|
||||
// 2. 脱敏认证头
|
||||
redactAuthHeader(c)
|
||||
|
||||
// 3. 脱敏 URL 查询参数
|
||||
redactQueryParams(c)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// LogrusLogger is a Gin middleware that logs requests using a Logrus logger.
|
||||
// It consumes redacted data prepared by the RedactionMiddleware.
|
||||
// shouldRedactBody 判断是否需要脱敏请求体
|
||||
func shouldRedactBody(c *gin.Context) bool {
|
||||
method := c.Request.Method
|
||||
contentType := c.GetHeader("Content-Type")
|
||||
|
||||
// 只处理包含 JSON 的 POST/PUT/PATCH/DELETE 请求
|
||||
return (method == "POST" || method == "PUT" || method == "PATCH" || method == "DELETE") &&
|
||||
strings.Contains(contentType, "application/json")
|
||||
}
|
||||
|
||||
// redactRequestBody 脱敏请求体
|
||||
func redactRequestBody(c *gin.Context) {
|
||||
bodyBytes, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 恢复请求体供后续使用
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
|
||||
// 脱敏敏感字段
|
||||
bodyString := string(bodyBytes)
|
||||
redactedBody := jsonSensitiveKeys.ReplaceAllString(bodyString, `$1`+RedactedValue)
|
||||
|
||||
c.Set(RedactedBodyKey, redactedBody)
|
||||
}
|
||||
|
||||
// redactAuthHeader 脱敏认证头
|
||||
func redactAuthHeader(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if bearerTokenPattern.MatchString(authHeader) {
|
||||
redacted := bearerTokenPattern.ReplaceAllString(authHeader, `${1}[REDACTED]`)
|
||||
c.Set(RedactedAuthHeaderKey, redacted)
|
||||
} else {
|
||||
// 对于非 Bearer 的 token,全部脱敏
|
||||
c.Set(RedactedAuthHeaderKey, "[REDACTED]")
|
||||
}
|
||||
|
||||
// 同时处理其他敏感 Header
|
||||
sensitiveHeaders := []string{"X-Api-Key", "X-Goog-Api-Key", "Api-Key"}
|
||||
for _, header := range sensitiveHeaders {
|
||||
if value := c.GetHeader(header); value != "" {
|
||||
c.Set("redacted_"+header, "[REDACTED]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// redactQueryParams 脱敏 URL 查询参数
|
||||
func redactQueryParams(c *gin.Context) {
|
||||
rawQuery := c.Request.URL.RawQuery
|
||||
if rawQuery == "" {
|
||||
return
|
||||
}
|
||||
|
||||
redacted := queryKeyPattern.ReplaceAllString(rawQuery, `${1}[REDACTED]`)
|
||||
if redacted != rawQuery {
|
||||
c.Set("redactedQuery", redacted)
|
||||
}
|
||||
}
|
||||
|
||||
// LogrusLogger Gin 请求日志中间件(使用 Logrus)
|
||||
func LogrusLogger(logger *logrus.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
path := c.Request.URL.Path
|
||||
method := c.Request.Method
|
||||
|
||||
// Process request
|
||||
// 处理请求
|
||||
c.Next()
|
||||
|
||||
// After request, gather data and log
|
||||
// 计算延迟
|
||||
latency := time.Since(start)
|
||||
statusCode := c.Writer.Status()
|
||||
clientIP := c.ClientIP()
|
||||
|
||||
entry := logger.WithFields(logrus.Fields{
|
||||
"status_code": statusCode,
|
||||
"latency_ms": latency.Milliseconds(),
|
||||
"client_ip": c.ClientIP(),
|
||||
"method": c.Request.Method,
|
||||
"path": path,
|
||||
})
|
||||
// 构建日志字段
|
||||
fields := logrus.Fields{
|
||||
"status": statusCode,
|
||||
"latency_ms": latency.Milliseconds(),
|
||||
"ip": clientIP,
|
||||
"method": method,
|
||||
"path": path,
|
||||
}
|
||||
|
||||
// 添加请求 ID(如果存在)
|
||||
if requestID := getRequestID(c); requestID != "" {
|
||||
fields["request_id"] = requestID
|
||||
}
|
||||
|
||||
// 添加脱敏后的数据
|
||||
if redactedBody, exists := c.Get(RedactedBodyKey); exists {
|
||||
entry = entry.WithField("body", redactedBody)
|
||||
fields["body"] = redactedBody
|
||||
}
|
||||
|
||||
if redactedAuth, exists := c.Get(RedactedAuthHeaderKey); exists {
|
||||
entry = entry.WithField("authorization", redactedAuth)
|
||||
fields["authorization"] = redactedAuth
|
||||
}
|
||||
|
||||
if redactedQuery, exists := c.Get("redactedQuery"); exists {
|
||||
fields["query"] = redactedQuery
|
||||
}
|
||||
|
||||
// 添加用户信息(如果已认证)
|
||||
if user := getAuthenticatedUser(c); user != "" {
|
||||
fields["user"] = user
|
||||
}
|
||||
|
||||
// 根据状态码选择日志级别
|
||||
entry := logger.WithFields(fields)
|
||||
|
||||
if len(c.Errors) > 0 {
|
||||
entry.Error(c.Errors.String())
|
||||
fields["errors"] = c.Errors.String()
|
||||
entry.Error("Request failed")
|
||||
} else {
|
||||
entry.Info("request handled")
|
||||
switch {
|
||||
case statusCode >= 500:
|
||||
entry.Error("Server error")
|
||||
case statusCode >= 400:
|
||||
entry.Warn("Client error")
|
||||
case statusCode >= 300:
|
||||
entry.Info("Redirect")
|
||||
default:
|
||||
// 只在 Debug 模式记录成功请求
|
||||
if logger.Level >= logrus.DebugLevel {
|
||||
entry.Debug("Request completed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getRequestID 获取请求 ID
|
||||
func getRequestID(c *gin.Context) string {
|
||||
if id, exists := c.Get("request_id"); exists {
|
||||
if requestID, ok := id.(string); ok {
|
||||
return requestID
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getAuthenticatedUser 获取已认证用户标识
|
||||
func getAuthenticatedUser(c *gin.Context) string {
|
||||
// 尝试从不同来源获取用户信息
|
||||
if user, exists := c.Get("adminUser"); exists {
|
||||
if authToken, ok := user.(interface{ GetID() string }); ok {
|
||||
return authToken.GetID()
|
||||
}
|
||||
}
|
||||
|
||||
if user, exists := c.Get("authToken"); exists {
|
||||
if authToken, ok := user.(interface{ GetID() string }); ok {
|
||||
return authToken.GetID()
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user