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; } };