You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用axum与tower_cookies创建缺失的会话Cookie并持久化到数据库?

解决方案:基于Axum和tower_cookies的自动会话中间件

要实现所有路由自动检测并创建会话Cookie,同时写入数据库的需求,核心是利用tower_cookies的Cookies提取器,结合Axum的中间件机制,避免直接处理原始HTTP头。以下是完整实现方案:

核心思路

  1. 先挂载CookieManagerLayer,它会自动处理Cookie的解析与序列化,并将Cookies实例注入请求扩展。
  2. 自定义中间件通过提取器直接获取Cookies和应用状态,完成会话检测、生成、存储逻辑。
  3. 将中间件全局挂载到所有路由,实现自动生效。

完整代码示例

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 15:12:04