mirror of
https://github.com/DullJZ/s3-balance.git
synced 2026-09-08 01:16:37 +08:00
Auth
This commit is contained in:
@@ -90,6 +90,7 @@ func main() {
|
|||||||
cfg.S3API.SecretKey,
|
cfg.S3API.SecretKey,
|
||||||
metricsService,
|
metricsService,
|
||||||
cfg.S3API.ProxyMode,
|
cfg.S3API.ProxyMode,
|
||||||
|
cfg.S3API.AuthRequired,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 注册配置热更新回调
|
// 注册配置热更新回调
|
||||||
|
|||||||
@@ -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,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驱动
|
||||||
|
|||||||
@@ -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", "")
|
||||||
|
}
|
||||||
@@ -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
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user