This commit is contained in:
DullJZ
2025-09-29 23:30:23 +08:00
parent eafb5c7aa7
commit 6af9165452
6 changed files with 65 additions and 18 deletions
+1
View File
@@ -90,6 +90,7 @@ func main() {
cfg.S3API.SecretKey, cfg.S3API.SecretKey,
metricsService, metricsService,
cfg.S3API.ProxyMode, cfg.S3API.ProxyMode,
cfg.S3API.AuthRequired,
) )
// 注册配置热更新回调 // 注册配置热更新回调
+1 -1
View File
@@ -137,5 +137,5 @@ s3api:
# true (默认):代理模式,数据通过S3 Balance服务器传输 # true (默认):代理模式,数据通过S3 Balance服务器传输
proxy_mode: true proxy_mode: true
# 是否需要认证(开发环境可设为false # 是否需要认证(开启后使用 Basic Auth,凭据来自 access_key/secret_key
auth_required: false auth_required: false
+1 -1
View File
@@ -1,5 +1,5 @@
# 多阶段构建用于减小镜像大小 # 多阶段构建用于减小镜像大小
FROM golang:1.24-alpine AS builder FROM golang:1.24.5-alpine AS builder
# 安装构建依赖 # 安装构建依赖
# 注意:不再需要 gcc, musl-dev, sqlite-dev,因为使用 modernc.org/sqlite 纯Go驱动 # 注意:不再需要 gcc, musl-dev, sqlite-dev,因为使用 modernc.org/sqlite 纯Go驱动
+57
View File
@@ -0,0 +1,57 @@
package api
import (
"encoding/base64"
"net/http"
"strings"
)
// authMiddleware 处理 Basic Auth 校验
func (h *S3Handler) authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !h.authRequired {
next.ServeHTTP(w, r)
return
}
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
h.requireAuth(w)
return
}
if strings.HasPrefix(authHeader, "Basic ") {
payload := strings.TrimPrefix(authHeader, "Basic ")
decoded, err := base64.StdEncoding.DecodeString(payload)
if err != nil {
h.requireAuth(w)
return
}
parts := strings.SplitN(string(decoded), ":", 2)
if len(parts) != 2 {
h.requireAuth(w)
return
}
if parts[0] != h.accessKey {
h.sendS3Error(w, "InvalidAccessKeyId", "The AWS Access Key Id you provided does not match the configured key.", "")
return
}
if parts[1] != h.secretKey {
h.sendS3Error(w, "SignatureDoesNotMatch", "The request signature we calculated does not match the signature you provided.", "")
return
}
next.ServeHTTP(w, r)
return
}
h.requireAuth(w)
})
}
func (h *S3Handler) requireAuth(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", "Basic realm=\"s3-balance\"")
h.sendS3Error(w, "AccessDenied", "Access Denied", "")
}
+4 -1
View File
@@ -19,6 +19,7 @@ type S3Handler struct {
secretKey string secretKey string
metrics *metrics.Metrics metrics *metrics.Metrics
proxyMode bool proxyMode bool
authRequired bool
} }
// NewS3Handler 创建新的S3兼容API处理器 // NewS3Handler 创建新的S3兼容API处理器
@@ -31,6 +32,7 @@ func NewS3Handler(
secretKey string, secretKey string,
metrics *metrics.Metrics, metrics *metrics.Metrics,
proxyMode bool, proxyMode bool,
authRequired bool,
) *S3Handler { ) *S3Handler {
return &S3Handler{ return &S3Handler{
bucketManager: bucketManager, bucketManager: bucketManager,
@@ -41,6 +43,7 @@ func NewS3Handler(
secretKey: secretKey, secretKey: secretKey,
metrics: metrics, metrics: metrics,
proxyMode: proxyMode, proxyMode: proxyMode,
authRequired: authRequired,
} }
} }
@@ -70,5 +73,5 @@ func (h *S3Handler) RegisterS3Routes(router *mux.Router) {
router.HandleFunc("/{bucket}/{key:.*}", h.handleObjectOperations).Methods("GET", "HEAD", "PUT", "DELETE") router.HandleFunc("/{bucket}/{key:.*}", h.handleObjectOperations).Methods("GET", "HEAD", "PUT", "DELETE")
// 添加认证中间件 // 添加认证中间件
router.Use(h.s3AuthMiddleware) router.Use(h.authMiddleware)
} }
+1 -15
View File
@@ -44,7 +44,7 @@ func (h *S3Handler) sendS3Error(w http.ResponseWriter, code string, message stri
statusCode = http.StatusNotFound statusCode = http.StatusNotFound
case "BucketAlreadyExists": case "BucketAlreadyExists":
statusCode = http.StatusConflict statusCode = http.StatusConflict
case "InvalidAccessKeyId", "SignatureDoesNotMatch": case "InvalidAccessKeyId", "SignatureDoesNotMatch", "AccessDenied":
statusCode = http.StatusForbidden statusCode = http.StatusForbidden
case "InternalError": case "InternalError":
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
@@ -67,20 +67,6 @@ func (h *S3Handler) setObjectHeaders(w http.ResponseWriter, obj *storage.Object)
} }
} }
// s3AuthMiddleware S3认证中间件(简化版)
func (h *S3Handler) s3AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 简化的认证实现,实际应该验证AWS Signature
// 这里只做基本的header检查
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
// 允许匿名访问(用于测试)
// 在生产环境中应该要求认证
}
next.ServeHTTP(w, r)
})
}
// 辅助函数:解析S3路径 // 辅助函数:解析S3路径
func parseS3Path(requestPath string) (bucket string, key string) { func parseS3Path(requestPath string) (bucket string, key string) {