Files
udmin/backend/src/middlewares/auth_guard.rs
ayou c462d266f1 feat(middleware): 添加全局认证拦截中间件
实现基于 Bearer token 的认证拦截中间件,支持路径白名单配置
2025-09-26 00:25:34 +08:00

91 lines
3.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 全局认证拦截中间件
//!
//! - 功能:对进入 /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 中实现。