feat(middleware): 添加全局认证拦截中间件
实现基于 Bearer token 的认证拦截中间件,支持路径白名单配置
This commit is contained in:
91
backend/src/middlewares/auth_guard.rs
Normal file
91
backend/src/middlewares/auth_guard.rs
Normal 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 中实现。
|
||||
Reference in New Issue
Block a user