feat(middleware): 添加全局认证拦截中间件

实现基于 Bearer token 的认证拦截中间件,支持路径白名单配置
This commit is contained in:
2025-09-26 00:25:34 +08:00
parent 214605d912
commit c462d266f1
3 changed files with 94 additions and 2 deletions

View File

@ -121,7 +121,8 @@ async fn main() -> anyhow::Result<()> {
let app = Router::new()
.nest("/api", api)
.layer(cors)
.layer(middleware::from_fn_with_state(db.clone(), middlewares::logging::request_logger));
.layer(middleware::from_fn_with_state(db.clone(), middlewares::logging::request_logger))
.layer(middleware::from_fn_with_state(db.clone(), middlewares::auth_guard::auth_guard));
// 读取并记录最终使用的主机与端口(默认端口改为 9898
let app_host = std::env::var("APP_HOST").unwrap_or("0.0.0.0".into());

View File

@ -0,0 +1,91 @@
//! 全局认证拦截中间件
//!
//! - 功能:对进入 /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<Db>,
mut req: Request<Body>,
next: Next,
) -> Result<Response, AppError> {
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 <token>
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::<u8>().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 中实现。

View File

@ -3,4 +3,4 @@ pub mod logging;
pub mod sse;
pub mod http_client;
pub mod ws;
// removed: pub mod sse_server;
pub mod auth_guard;