feat(middleware): 添加全局认证拦截中间件
实现基于 Bearer token 的认证拦截中间件,支持路径白名单配置
This commit is contained in:
@ -121,7 +121,8 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest("/api", api)
|
.nest("/api", api)
|
||||||
.layer(cors)
|
.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)
|
// 读取并记录最终使用的主机与端口(默认端口改为 9898)
|
||||||
let app_host = std::env::var("APP_HOST").unwrap_or("0.0.0.0".into());
|
let app_host = std::env::var("APP_HOST").unwrap_or("0.0.0.0".into());
|
||||||
|
|||||||
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 中实现。
|
||||||
@ -3,4 +3,4 @@ pub mod logging;
|
|||||||
pub mod sse;
|
pub mod sse;
|
||||||
pub mod http_client;
|
pub mod http_client;
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
// removed: pub mod sse_server;
|
pub mod auth_guard;
|
||||||
Reference in New Issue
Block a user