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

使用actix_session与RedisActorSessionStore跨路由持久化数据遇阻

问题:Actix Web会话无法跨路由持久化,Redis中已存数据但其他路由无法读取

将会话数据写入Redis后,无法在其他路由中检索到这些数据。登录路由中插入会话数据后,能通过session.entries()或session.get::<>()立即查看数据,但其他路由中会话始终为空,推测会话无法跨路由持久化。我对浏览器端会话存储机制了解有限,只知道它是安全存储且随HTTP请求自动发送,难以判断问题出在会话数据发送到浏览器的环节,还是从浏览器检索会话的环节。

登录路由代码

#[post("/login")]
async fn login(session: Session, user_info: web::Json<LoginDetails>) -> impl Responder {

...
// 密码验证逻辑
...
session.insert("user_id".to_string(), user_id);
session.insert("status_level".to_string(), status_level);
session.insert("login_status".to_string(), true);

let user_session = UserSession {
    session_string: session_string.to_string(),
    session_details
};

dbg!(session.entries()); // 这里能正常显示会话条目 :)
dbg!(session.get::<u8>("user_id"));

return HttpResponse::Ok().status(StatusCode::from_u16(200).unwrap()).json(user_session);
},
...

会话验证函数

pub fn validate_session(session: &Session) -> Result<u8, HttpResponse> {
    let user_id: Option<u8> = session.get("user_id").unwrap_or_else(|err| { 
        println!("Error getting user_id from session: {}", err); 
        None 
    });

    dbg!(session.entries()); // 这里会话为空 :(

    dbg!(&user_id);
    match user_id {
        Some(id) => {
            // 延长会话有效期
            session.renew();
            Ok(id)
        }
        None => Err(HttpResponse::Unauthorized().json("Unauthorized")),
    }
}

受会话保护的路由

#[get("/campaign_names")]
async fn campaign_names(session: Session, req: HttpRequest) -> impl Responder {

    let id = validate_session(&session).unwrap_or(0);

    println!("id {}",id); // 输出 0
...
}

主入口路由配置

#[actix_web::main]
async fn main() -> std::io::Result<()> {

    let secret_key = Key::generate();

    HttpServer::new(move || {
        let cors = Cors::permissive();
        App::new()
            .wrap(cors)
            .wrap(
                SessionMiddleware::builder(
                    RedisActorSessionStore::new("127.0.0.1:6379"),
                    secret_key.clone(),
                )
                    // 允许JavaScript访问Cookie
                    .cookie_http_only(false)
                    // 仅允许当前域名使用Cookie
                    .cookie_same_site(SameSite::Strict)
                    .build(),
            )
            .service(campaign_names)
...
}

补充信息

  • 已确认Redis中已写入会话数据,尝试过非Actor会话存储,问题依旧。
  • 参考过官方认证示例,推测会话ID的发送与返回应该由中间件自动处理,但实际未生效。

解决方案

1. 修复跨域请求的凭证携带问题

如果前端与后端是跨域部署,必须让前端请求显式携带凭证:

  • Axios:设置withCredentials: true
    axios.get('/campaign_names', { withCredentials: true })
    
  • Fetch API:设置credentials: 'include'
    fetch('/campaign_names', { credentials: 'include' })
    

浏览器默认不会在跨域请求中携带Cookie,即使你设置了Cors::permissive(),也必须开启这个配置。

2. 检查会话Cookie的响应头设置

登录请求完成后,查看响应头中的Set-Cookie字段,确认:

  • Cookie的Domain设置正确,与前端域名匹配(如果跨域)
  • 若使用SameSite::Strict,需确保请求是同域发起;跨域场景可改为SameSite::Lax,如果是HTTPS环境可尝试SameSite::None(需配合cookie_secure(true))

3. 确保会话数据及时提交到Redis

在登录路由中显式调用session.commit().await,强制将会话数据写入Redis:

// 插入会话数据后添加
session.commit().await?;

虽然Actix默认会在响应发送时自动提交,但显式调用可以避免异步场景下的提交延迟。

4. 验证Redis连接与会话数据

  • 登录后在Redis中执行KEYS *,查看是否存在会话键(通常格式为actix-session:<会话ID>)
  • 使用GET <会话键>查看存储的内容,确认user_id等字段是否存在

5. 固定会话密钥

不要每次服务重启都生成新的secret_key,否则旧会话的签名会失效,导致无法读取。可以将密钥存储在配置文件中,每次启动加载:

// 从配置文件读取密钥,示例:
let secret_key = Key::from(base64::decode("你的固定密钥").unwrap());

内容的提问来源于stack exchange,提问作者Garrett Berg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 21:49:51