如何用axum与tower_cookies创建缺失的会话Cookie并持久化到数据库?
要实现所有路由自动检测并创建会话Cookie,同时写入数据库的需求,核心是利用tower_cookies的Cookies提取器,结合Axum的中间件机制,避免直接处理原始HTTP头。以下是完整实现方案:
核心思路
- 先挂载
CookieManagerLayer,它会自动处理Cookie的解析与序列化,并将Cookies实例注入请求扩展。 - 自定义中间件通过提取器直接获取
Cookies和应用状态,完成会话检测、生成、存储逻辑。 - 将中间件全局挂载到所有路由,实现自动生效。
完整代码示例
1. 依赖配置(Cargo.toml)
axum = "0.7" tower-cookies = "0.10" rand = "0.8" # 用于生成安全的会话ID # 替换为你的数据库依赖(如sqlx、tokio-postgres等)
2. 实现代码
use axum::{ extract::{State, Request, Next}, middleware, response::Response, routing::get, Router, }; use rand::Rng; use tower_cookies::{Cookies, Cookie}; use std::sync::Arc; use time::Duration; // 应用状态:包含数据库连接(用Arc实现线程安全共享) #[derive(Clone)] struct MyState { db: Arc<dyn DatabaseOps + Send + Sync>, } // 数据库操作抽象 trait,适配你的实际数据库逻辑 trait DatabaseOps { async fn save_session(&self, session_id: &str, metadata: &str) -> Result<(), DbError>; } // 自定义数据库错误类型,根据实际场景调整 #[derive(Debug)] struct DbError; // 全局会话中间件:自动检测并创建会话 async fn session_middleware<B>( State(state): State<MyState>, mut cookies: Cookies, request: Request<B>, next: Next<B>, ) -> Response { const SESSION_COOKIE_NAME: &str = "session_id"; // 检查请求中是否存在会话Cookie if cookies.get(SESSION_COOKIE_NAME).is_none() { // 生成32位随机会话ID(生产环境可改用ring等安全随机库) let session_id = rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) .take(32) .map(char::from) .collect::<String>(); // 将会话信息写入数据库(示例元数据可替换为请求相关信息) if let Err(e) = state.db.save_session(&session_id, "user_agent: ...").await { eprintln!("会话存储失败: {:?}", e); // 可根据需求返回500错误,此处简化为继续处理请求 } // 配置并添加会话Cookie(生产环境务必开启安全属性) let session_cookie = Cookie::build((SESSION_COOKIE_NAME, session_id)) .http_only(true) // 阻止JS读取,防范XSS .secure(true) // 仅HTTPS传输,生产环境启用 .same_site(tower_cookies::SameSite::Lax) .path("/") // 所有路由生效 .max_age(Duration::days(7)) // 有效期7天 .finish(); cookies.add(session_cookie); } // 传递请求到下一层处理 next.run(request).await } // 示例路由处理器 async fn hello() -> &'static str { "Hello, World!" } #[tokio::main] async fn main() { // 初始化数据库连接(替换为你的实际初始化逻辑) let db = Arc::new(MyDatabaseImpl {}); let state = MyState { db }; // 构建路由:中间件全局应用到所有路由 let app = Router::new() .route("/", get(hello)) .route("/dashboard", get(hello)) .layer( tower::ServiceBuilder::new() // 必须先挂载CookieManagerLayer,否则Cookies提取器无法工作 .layer(tower_cookies::CookieManagerLayer::new()) .layer(middleware::from_fn_with_state(state.clone(), session_middleware)), ); // 启动服务 axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); } // 数据库操作实现示例,替换为你的实际代码 struct MyDatabaseImpl; impl DatabaseOps for MyDatabaseImpl { async fn save_session(&self, session_id: &str, metadata: &str) -> Result<(), DbError> { // 此处编写实际的数据库插入逻辑,如SQLx的execute语句 println!("已存储会话: {},元数据: {}", session_id, metadata); Ok(()) } }
关键注意事项
- 中间件顺序:
CookieManagerLayer必须在自定义会话中间件之前挂载,否则Cookies提取器无法从请求中获取实例。 - Cookie安全配置:生产环境必须启用
secure(仅HTTPS)、http_only(防XSS)、same_site(防CSRF)属性。 - 会话ID生成:示例用rand crate,生产环境建议使用
ring等提供密码学安全的随机数生成库。 - 错误处理:数据库操作失败时,可根据业务需求返回500错误或记录日志后继续处理请求。
内容的提问来源于stack exchange,提问作者druckermanly
相关产品推荐
相关产品推荐

