//! 全局认证拦截中间件 //! //! - 功能:对进入 /api 的请求进行统一的登录校验(Bearer access token) //! - 支持按路径跳过认证:前缀白名单与精确路径白名单 //! - 失败返回 401,消息体统一为 { code: 401, message: "unauthorized" } use axum::{extract::State, http::{Request, Method, header::AUTHORIZATION}, middleware::Next, response::Response}; use axum::body::Body; use crate::{db::Db, error::AppError}; /// 路径白名单配置 /// - prefix_whitelist:按前缀匹配(例如 /api/auth/) /// - exact_whitelist:按完整路径匹配(例如 /api/auth/login) #[derive(Clone, Debug)] pub struct AuthGuardConfig { pub prefix_whitelist: Vec<&'static str>, pub exact_whitelist: Vec<&'static str>, } impl Default for AuthGuardConfig { fn default() -> Self { Self { // 登录/刷新/公开动态接口等路径前缀允许匿名访问 prefix_whitelist: vec![ "/api/auth/", "/api/dynamic_api/public/", ], // 精确路径白名单:如健康检查等 exact_whitelist: vec![ "/api/auth/login", "/api/auth/refresh", ], } } } /// 全局认证拦截中间件 pub async fn auth_guard( State(_db): State, mut req: Request, next: Next, ) -> Result { let path = req.uri().path(); // CORS 预检请求直接放行 if req.method() == Method::OPTIONS { return Ok(next.run(req).await); } // 读取白名单配置(后续可支持从环境变量扩展) let cfg = AuthGuardConfig::default(); let is_whitelisted = cfg.exact_whitelist.iter().any(|&x| x == path) || cfg.prefix_whitelist.iter().any(|&p| path.starts_with(p)); if is_whitelisted { return Ok(next.run(req).await); } // 仅拦截 /api 下的受保护接口;其他如 /ws /sse 在各自模块中单独校验 if !path.starts_with("/api/") { return Ok(next.run(req).await); } // 从请求头解析 Authorization: Bearer let auth = req.headers().get(AUTHORIZATION).ok_or(AppError::Unauthorized)?; let auth = auth.to_str().map_err(|_| AppError::Unauthorized)?; let token = auth.strip_prefix("Bearer ").ok_or(AppError::Unauthorized)?; let secret = std::env::var("JWT_SECRET").map_err(|_| AppError::Unauthorized)?; // 解析并校验访问令牌 let claims = crate::middlewares::jwt::decode_token(token, &secret)?; if claims.typ != "access" { return Err(AppError::Unauthorized); } // 可选:Redis 二次校验(与 AuthUser 提取器一致) let redis_validation_enabled = std::env::var("REDIS_TOKEN_VALIDATION") .ok() .and_then(|v| v.parse::().ok()) .map(|x| x == 1) .unwrap_or(false); if redis_validation_enabled { let is_valid = crate::redis::TokenRedis::validate_access_token(token, claims.uid).await.unwrap_or(false); if !is_valid { return Err(AppError::Unauthorized); } } // 将用户信息注入扩展,供后续处理链使用(可选) req.extensions_mut().insert(claims); Ok(next.run(req).await) } // 统一将 AppError 转换为响应说明:AppError 的 IntoResponse 已在 error.rs 中实现。