From a66d59e86ba3ce29e0b7e91d498bf9d15cbb9bc6 Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 28 Apr 2026 01:30:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(server):=20Rate=20limit=20fail-close=20?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E6=8E=A7?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 开发环境默认 fail-open(Redis 不可达时放行), 生产环境设置 ERP__RATE_LIMIT__FAIL_CLOSE=true 启用 fail-close(返回 503)。 --- .../erp-server/src/middleware/rate_limit.rs | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/crates/erp-server/src/middleware/rate_limit.rs b/crates/erp-server/src/middleware/rate_limit.rs index c273fc0..b75dd19 100644 --- a/crates/erp-server/src/middleware/rate_limit.rs +++ b/crates/erp-server/src/middleware/rate_limit.rs @@ -180,13 +180,21 @@ pub async fn account_lockout_middleware( ) -> Response { let avail = redis_avail(); - // Redis 不可达时 fail-close:拒绝登录请求(安全优先) + // Redis 可达性检查:生产环境 fail-close,开发环境 fail-open(通过环境变量控制) + let fail_close = std::env::var("ERP__RATE_LIMIT__FAIL_CLOSE") + .map(|v| v == "true" || v == "1") + .unwrap_or(false); + if !avail.should_try().await { - tracing::error!("Redis 不可达,fail-close 拒绝登录请求"); - return (StatusCode::SERVICE_UNAVAILABLE, axum::Json(RateLimitResponse { - error: "service_unavailable".to_string(), - message: "安全服务暂不可用,请稍后重试".to_string(), - })).into_response(); + if fail_close { + tracing::error!("Redis 不可达,fail-close 拒绝登录请求"); + return (StatusCode::SERVICE_UNAVAILABLE, axum::Json(RateLimitResponse { + error: "service_unavailable".to_string(), + message: "安全服务暂不可用,请稍后重试".to_string(), + })).into_response(); + } + tracing::error!("Redis 不可达,fail-open 放行(非生产模式,建议设置 ERP__RATE_LIMIT__FAIL_CLOSE=true)"); + return next.run(req).await; } // 获取 Redis 连接 @@ -196,12 +204,16 @@ pub async fn account_lockout_middleware( c } Err(e) => { - tracing::error!(error = %e, "Redis 连接失败,fail-close 拒绝登录请求"); avail.mark_failed().await; - return (StatusCode::SERVICE_UNAVAILABLE, axum::Json(RateLimitResponse { - error: "service_unavailable".to_string(), - message: "安全服务暂不可用,请稍后重试".to_string(), - })).into_response(); + if fail_close { + tracing::error!(error = %e, "Redis 连接失败,fail-close 拒绝登录请求"); + return (StatusCode::SERVICE_UNAVAILABLE, axum::Json(RateLimitResponse { + error: "service_unavailable".to_string(), + message: "安全服务暂不可用,请稍后重试".to_string(), + })).into_response(); + } + tracing::error!(error = %e, "Redis 连接失败,fail-open 放行(非生产模式)"); + return next.run(req).await; } };