Skip to main content

sa_token_core/
token_session.rs

1// Author: 金书记
2//
3//! Token-Session 双轨(独立于 Account-Session)。
4//!
5//! Token-Session 与 Account-Session 是两条独立的数据轨:
6//! - Account-Session 按账号聚合,同账号多设备共享同一份
7//! - Token-Session 按 token 隔离,每次登录各有一份,token 下线即销毁
8
9use crate::error::{SaTokenError, SaTokenResult};
10use crate::manager::SaTokenManager;
11use crate::session::SaSession;
12use crate::token::TokenValue;
13
14impl SaTokenManager {
15    /// Token-Session 的 id 命名规则(逻辑 id,非存储键)
16    fn token_session_id(token: &str) -> String {
17        format!("token-session:{token}")
18    }
19
20    /// 获取 Token-Session(不存在时按配置决定是否落盘创建)
21    pub async fn get_token_session(&self, token: &TokenValue) -> SaTokenResult<SaSession> {
22        if self.config.token_session_check_login && !self.is_valid(token).await {
23            return Err(SaTokenError::NotLogin);
24        }
25
26        if let Some(session) = self.session_repo().get_token_session(token).await? {
27            return Ok(session);
28        }
29
30        let session = SaSession::new(Self::token_session_id(token.as_str()));
31        if self.config.right_now_create_token_session {
32            self.session_repo()
33                .save_token_session(token, &session)
34                .await?;
35        }
36        Ok(session)
37    }
38
39    /// 匿名 Token-Session:跳过登录校验,且从不自动落盘
40    pub async fn get_anon_token_session(&self, token: &TokenValue) -> SaTokenResult<SaSession> {
41        if let Some(session) = self.session_repo().get_token_session(token).await? {
42            return Ok(session);
43        }
44        Ok(SaSession::new(Self::token_session_id(token.as_str())))
45    }
46
47    /// 保存 Token-Session(TTL 与 token 生命周期对齐)
48    pub async fn save_token_session(
49        &self,
50        token: &TokenValue,
51        session: &SaSession,
52    ) -> SaTokenResult<()> {
53        self.session_repo().save_token_session(token, session).await
54    }
55
56    /// 删除 Token-Session
57    pub async fn delete_token_session(&self, token: &TokenValue) -> SaTokenResult<()> {
58        self.session_repo().delete_token_session(token).await
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use crate::config::SaTokenConfig;
66    use sa_token_storage_memory::MemoryStorage;
67    use std::sync::Arc;
68
69    #[tokio::test]
70    async fn test_token_session_create_and_delete() {
71        let config = SaTokenConfig {
72            right_now_create_token_session: true,
73            ..Default::default()
74        };
75        let mgr = SaTokenManager::new(Arc::new(MemoryStorage::new()), config);
76        let token = mgr.login("u1").await.unwrap();
77        let mut session = mgr.get_token_session(&token).await.unwrap();
78        assert!(session.id.starts_with("token-session:"));
79        session.set("k", "v").unwrap();
80        mgr.save_token_session(&token, &session).await.unwrap();
81        let loaded = mgr.get_token_session(&token).await.unwrap();
82        assert_eq!(loaded.get::<String>("k"), Some("v".to_string()));
83        mgr.delete_token_session(&token).await.unwrap();
84        // 灰盒:存储键必须消失(不能只靠 get 重建后看空字段)
85        let key = mgr.keys().token_session(token.as_str());
86        let raw = mgr.storage().get(&key).await.unwrap();
87        assert!(raw.is_none(), "token_session key must be deleted");
88        let after = mgr.get_token_session(&token).await.unwrap();
89        assert!(after.get::<String>("k").is_none());
90    }
91}