Skip to main content

sa_token_core/
manager.rs

1// Author: 金书记
2//
3//! Token 管理器 - sa-token 的核心入口
4
5use std::sync::Arc;
6use chrono::{DateTime, Duration, Utc};
7use sa_token_adapter::storage::SaStorage;
8use crate::config::{LogoutMode, ReplacedLoginExitMode, SaTokenConfig};
9use crate::error::{SaTokenError, SaTokenResult};
10use crate::token::{TokenInfo, TokenValue, TokenGenerator};
11use crate::token::map::{
12    TOKEN_MAP_BE_REPLACED, TOKEN_MAP_KICK_OUT, is_kick_out_marker, is_replaced_marker,
13};
14use crate::session::SaSession;
15use crate::event::{SaTokenEventBus, SaTokenEvent};
16use crate::online::OnlineManager;
17use crate::distributed::DistributedSessionManager;
18use crate::nonce::NonceManager;
19use crate::refresh::RefreshTokenManager;
20use crate::stp_interface::StpInterface;
21
22/// sa-token 管理器
23#[derive(Clone)]
24pub struct SaTokenManager {
25    /// 底层存储适配器
26    pub(crate) storage: Arc<dyn SaStorage>,
27    /// 配置信息
28    pub config: SaTokenConfig,
29    /// 事件总线
30    pub(crate) event_bus: SaTokenEventBus,
31    /// 在线用户管理器
32    online_manager: Option<Arc<OnlineManager>>,
33    /// 分布式 Session 管理器
34    distributed_manager: Option<Arc<DistributedSessionManager>>,
35    /// 权限/角色/封禁数据源回调
36    pub(crate) stp_interface: Option<Arc<dyn StpInterface>>,
37}
38
39impl SaTokenManager {
40    /// 创建新的管理器实例
41    pub fn new(storage: Arc<dyn SaStorage>, config: SaTokenConfig) -> Self {
42        Self {
43            storage,
44            config,
45            event_bus: SaTokenEventBus::new(),
46            online_manager: None,
47            distributed_manager: None,
48            stp_interface: None,
49        }
50    }
51
52    pub fn with_stp_interface(mut self, iface: Arc<dyn StpInterface>) -> Self {
53        self.stp_interface = Some(iface);
54        self
55    }
56    
57    pub fn with_online_manager(mut self, manager: Arc<OnlineManager>) -> Self {
58        self.online_manager = Some(manager);
59        self
60    }
61    
62    pub fn with_distributed_manager(mut self, manager: Arc<DistributedSessionManager>) -> Self {
63        self.distributed_manager = Some(manager);
64        self
65    }
66    
67    pub fn online_manager(&self) -> Option<&Arc<OnlineManager>> {
68        self.online_manager.as_ref()
69    }
70    
71    pub fn distributed_manager(&self) -> Option<&Arc<DistributedSessionManager>> {
72        self.distributed_manager.as_ref()
73    }
74    
75    /// 获取事件总线的引用
76    pub fn event_bus(&self) -> &SaTokenEventBus {
77        &self.event_bus
78    }
79    
80    /// 登录:为指定账号创建 token
81    pub async fn login(&self, login_id: impl Into<String>) -> SaTokenResult<TokenValue> {
82        self.login_with_options(login_id, None, None, None, None, None).await
83    }
84    
85    /// 登录:为指定账号创建 token(支持自定义 TokenInfo 字段)
86    /// 
87    /// # 参数 | Parameters
88    /// * `login_id` - 登录用户 ID | Login user ID
89    /// * `login_type` - 登录类型(如 "user", "admin")| Login type (e.g., "user", "admin")
90    /// * `device` - 设备标识 | Device identifier
91    /// * `extra_data` - 额外数据 | Extra data
92    /// * `nonce` - 防重放攻击的一次性令牌 | One-time token for replay attack prevention
93    /// * `expire_time` - 自定义过期时间(如果为 None,则使用配置的过期时间)| Custom expiration time (if None, use configured timeout)
94    /// 
95    /// # 示例 | Example
96    /// ```rust,ignore
97    /// let token = manager.login_with_options(
98    ///     "user_123",
99    ///     Some("admin".to_string()),
100    ///     Some("iPhone".to_string()),
101    ///     Some(json!({"ip": "192.168.1.1"})),
102    ///     Some("nonce_123".to_string()),
103    ///     None,
104    /// ).await?;
105    /// ```
106    pub async fn login_with_options(
107        &self,
108        login_id: impl Into<String>,
109        login_type: Option<String>,
110        device: Option<String>,
111        extra_data: Option<serde_json::Value>,
112        nonce: Option<String>,
113        expire_time: Option<DateTime<Utc>>,
114    ) -> SaTokenResult<TokenValue> {
115        let login_id = login_id.into();
116        
117        // 生成 token(支持 JWT,如果有 extra_data 则签入 token)
118        let token = match &extra_data {
119            Some(extra) => TokenGenerator::generate_with_login_id_and_extra(&self.config, &login_id, extra),
120            None => TokenGenerator::generate_with_login_id(&self.config, &login_id),
121        };
122        
123        // 创建 token 信息
124        let mut token_info = TokenInfo::new(token.clone(), login_id.clone());
125        
126        // 设置登录类型
127        token_info.login_type = login_type.unwrap_or_else(|| "default".to_string());
128        
129        // 设置设备标识
130        if let Some(device_str) = device {
131            token_info.device = Some(device_str);
132        }
133        
134        // 设置额外数据
135        if let Some(extra) = extra_data {
136            token_info.extra_data = Some(extra);
137        }
138        
139        // 设置 nonce
140        if let Some(nonce_str) = nonce {
141            token_info.nonce = Some(nonce_str);
142        }
143        
144        // 设置过期时间
145        if let Some(custom_expire_time) = expire_time {
146            token_info.expire_time = Some(custom_expire_time);
147        }
148        // 注意:如果 expire_time 为 None,login_with_token_info 会自动使用配置的过期时间
149        
150        // 调用底层方法
151        self.login_with_token_info(token_info).await
152    }
153    
154    /// 登录:使用完整的 TokenInfo 对象创建 token
155    /// 
156    /// # 参数 | Parameters
157    /// * `token_info` - 完整的 TokenInfo 对象,包含所有 token 信息 | Complete TokenInfo object containing all token information
158    /// 
159    /// # 说明 | Notes
160    /// * TokenInfo 中的 `token` 字段将被使用(如果已设置),否则会自动生成
161    /// * TokenInfo 中的 `login_id` 字段必须设置
162    /// * 如果 `expire_time` 为 None,将使用配置的过期时间
163    /// * The `token` field in TokenInfo will be used (if set), otherwise will be auto-generated
164    /// * The `login_id` field in TokenInfo must be set
165    /// * If `expire_time` is None, will use configured timeout
166    /// 
167    /// # 示例 | Example
168    /// ```rust,ignore
169    /// use sa_token_core::token::{TokenInfo, TokenValue};
170    /// use chrono::Utc;
171    /// 
172    /// let mut token_info = TokenInfo::new(
173    ///     TokenValue::new("custom_token_123"),
174    ///     "user_123"
175    /// );
176    /// token_info.login_type = "admin".to_string();
177    /// token_info.device = Some("iPhone".to_string());
178    /// token_info.extra_data = Some(json!({"ip": "192.168.1.1"}));
179    /// 
180    /// let token = manager.login_with_token_info(token_info).await?;
181    /// ```
182    pub async fn login_with_token_info(&self, mut token_info: TokenInfo) -> SaTokenResult<TokenValue> {
183        let login_id = token_info.login_id.clone();
184        
185        // 如果 token_info 中没有 token,则生成一个
186        let token = if token_info.token.as_str().is_empty() {
187            TokenGenerator::generate_with_login_id(&self.config, &login_id)
188        } else {
189            token_info.token.clone()
190        };
191        
192        // 更新 token_info 中的 token
193        token_info.token = token.clone();
194        
195        // 更新最后活跃时间为当前时间
196        token_info.update_active_time();
197        
198        // 如果过期时间为 None,使用配置的过期时间
199        let now = Utc::now();
200        if token_info.expire_time.is_none()
201            && let Some(timeout) = self.config.timeout_duration() {
202                token_info.expire_time = Some(now + Duration::from_std(timeout).unwrap());
203            }
204        
205        // 确保登录类型不为空
206        if token_info.login_type.is_empty() {
207            token_info.login_type = "default".to_string();
208        }
209        
210        // 计算 login_id -> token 映射键(非并发踢旧与写入映射均依赖此键)
211        let login_token_key = self.login_token_mapping_key(&login_id, &token_info.login_type);
212
213        // is_share:同 login_id + login_type 复用已有 token
214        if self.config.is_share {
215            if let Ok(Some(existing)) = self.storage.get(&login_token_key).await {
216                let existing_token = TokenValue::new(existing);
217                if self.is_valid(&existing_token).await {
218                    return Ok(existing_token);
219                }
220            }
221        }
222
223        // 启用 nonce 时:登录前校验并消费一次性 nonce,防止重放
224        if self.config.enable_nonce
225            && let Some(ref nonce_str) = token_info.nonce {
226                let nonce_timeout = if self.config.nonce_timeout > 0 {
227                    self.config.nonce_timeout
228                } else {
229                    self.config.timeout
230                };
231                let nonce_mgr = NonceManager::new(self.storage.clone(), nonce_timeout);
232                nonce_mgr.validate_and_consume(nonce_str, &login_id).await?;
233            }
234
235        // 非并发登录:顶旧 token(replaced)或拒绝新登录
236        if !self.config.is_concurrent
237            && let Ok(Some(old_token)) = self.storage.get(&login_token_key).await
238            && old_token != token.as_str() {
239                match self.config.replaced_login_exit_mode {
240                    ReplacedLoginExitMode::OldDevice => {
241                        self.replaced_by_token(&TokenValue::new(old_token)).await?;
242                    }
243                    ReplacedLoginExitMode::NewDevice => {
244                        return Err(SaTokenError::AccountReplaced);
245                    }
246                }
247            }
248
249        // 启用 Refresh Token 时预生成并写入 TokenInfo
250        let refresh_mgr = if self.config.enable_refresh_token {
251            Some(RefreshTokenManager::new(
252                self.storage.clone(),
253                Arc::new(self.config.clone()),
254            ))
255        } else {
256            None
257        };
258        if let Some(ref mgr) = refresh_mgr {
259            let rt = mgr.generate(&login_id);
260            token_info.refresh_token = Some(rt);
261            if self.config.refresh_token_timeout > 0 {
262                token_info.refresh_token_expire_time = Some(
263                    Utc::now() + Duration::seconds(self.config.refresh_token_timeout),
264                );
265            }
266        }
267        
268        // 存储 token 信息
269        let key = self.config.make_key("token:", token.as_str());
270        let value = serde_json::to_string(&token_info)
271            .map_err(SaTokenError::SerializationError)?;
272        
273        self.storage.set(&key, &value, self.config.timeout_duration()).await
274            .map_err(|e| SaTokenError::StorageError(e.to_string()))?;
275        
276        // 保存 login_id 到 token 的映射
277        self.storage.set(&login_token_key, token.as_str(), self.config.timeout_duration()).await
278            .map_err(|e| SaTokenError::StorageError(e.to_string()))?;
279
280        // token -> login_id 反向映射(kickout/replaced 标记依赖此键)
281        self.save_token_id_mapping(token.as_str(), &login_id).await?;
282
283        let account_ns = self.account_ns(&token_info.login_type, &login_id);
284
285        // 维护多设备 token 列表(并发场景下 get_all_tokens_by_login_id 依赖此索引)
286        self.append_token_index(&account_ns, token.as_str()).await?;
287
288        // 在 Account-Session 上记录本次登录的终端信息
289        {
290            let mut session = self.get_session(&account_ns).await?;
291            let mut terminal = crate::session::SaTerminalInfo::new(
292                token.as_str(),
293                token_info.device.as_deref().unwrap_or(""),
294            );
295            if let Some(extra) = token_info.extra_data.clone() {
296                terminal = terminal.with_extra_data(extra);
297            }
298            session.add_terminal(terminal);
299            self.save_session(&session).await?;
300        }
301
302        self.enforce_max_login_count(&account_ns).await?;
303
304        if self.config.right_now_create_token_session {
305            let session = SaSession::new(format!("token-session:{}", token.as_str()));
306            let _ = self.save_token_session(&token, &session).await;
307        }
308
309        // 持久化 refresh token 与 access token 的关联
310        if let Some(ref mgr) = refresh_mgr
311            && let Some(ref rt) = token_info.refresh_token {
312                mgr.store_with_extra(
313                    rt,
314                    token.as_str(),
315                    &login_id,
316                    token_info.extra_data.as_ref(),
317                )
318                .await?;
319            }
320        
321        // 触发登录事件
322        let event = SaTokenEvent::login(login_id.clone(), token.as_str())
323            .with_login_type(&token_info.login_type);
324        self.event_bus.publish(event).await;
325        
326        Ok(token)
327    }
328    
329    /// 登出:删除指定 token(LOGOUT 模式)
330    pub async fn logout(&self, token: &TokenValue) -> SaTokenResult<()> {
331        self.logout_internal(token, LogoutMode::Logout, self.config.is_logout_keep_token_session)
332            .await
333    }
334
335    /// 踢人下线(KICKOUT 模式:保留映射标记 -5)
336    pub async fn kick_out_by_token(&self, token: &TokenValue) -> SaTokenResult<()> {
337        self.logout_internal(token, LogoutMode::KickOut, self.config.is_logout_keep_token_session)
338            .await
339    }
340
341    /// 顶号下线(REPLACED 模式:保留映射标记 -4)
342    pub async fn replaced_by_token(&self, token: &TokenValue) -> SaTokenResult<()> {
343        self.logout_internal(token, LogoutMode::Replaced, self.config.is_logout_keep_token_session)
344            .await
345    }
346
347    async fn logout_internal(
348        &self,
349        token: &TokenValue,
350        mode: LogoutMode,
351        keep_token_session: bool,
352    ) -> SaTokenResult<()> {
353        tracing::debug!("Manager: logout_internal mode={:?}, token={}", mode, token);
354
355        let key = self.config.make_key("token:", token.as_str());
356        let token_info_str = self
357            .storage
358            .get(&key)
359            .await
360            .map_err(|e| SaTokenError::StorageError(e.to_string()))?;
361
362        let token_info = token_info_str
363            .as_ref()
364            .and_then(|value| serde_json::from_str::<TokenInfo>(value).ok());
365
366        let login_id = if let Some(ref info) = token_info {
367            Some(info.login_id.clone())
368        } else if let Ok(Some(mapped)) = self.get_token_id_mapping(token.as_str()).await {
369            if is_kick_out_marker(&mapped) || is_replaced_marker(&mapped) {
370                None
371            } else {
372                Some(mapped)
373            }
374        } else {
375            None
376        };
377
378        if mode == LogoutMode::Logout {
379            self.storage
380                .delete(&key)
381                .await
382                .map_err(|e| SaTokenError::StorageError(e.to_string()))?;
383            self.delete_token_id_mapping(token.as_str()).await?;
384        } else if mode == LogoutMode::KickOut {
385            self.update_token_id_mapping(token.as_str(), TOKEN_MAP_KICK_OUT)
386                .await?;
387        } else {
388            self.update_token_id_mapping(token.as_str(), TOKEN_MAP_BE_REPLACED)
389                .await?;
390        }
391
392        if !keep_token_session {
393            let _ = self.delete_token_session(token).await;
394        }
395
396        if let Some(info) = token_info {
397            let account_ns = self.account_ns(&info.login_type, &info.login_id);
398
399            if let Ok(mut session) = self.get_session(&account_ns).await {
400                if session.remove_terminal(token.as_str()).is_some() {
401                    if session.terminal_count() == 0 && mode != LogoutMode::Replaced {
402                        let _ = self.delete_session(&account_ns).await;
403                    } else {
404                        let _ = self.save_session(&session).await;
405                    }
406                }
407            }
408
409            let login_token_key =
410                self.login_token_mapping_key(&info.login_id, &info.login_type);
411            if mode == LogoutMode::Logout {
412                if let Ok(Some(mapped)) = self.storage.get(&login_token_key).await
413                    && mapped == token.as_str() {
414                        let _ = self.storage.delete(&login_token_key).await;
415                    }
416                let _ = self.remove_token_index(&account_ns, token.as_str()).await;
417            }
418
419            if let Some(online_mgr) = &self.online_manager {
420                online_mgr.mark_offline(&info.login_id, token.as_str()).await;
421            }
422
423            let event = match mode {
424                LogoutMode::Logout => {
425                    SaTokenEvent::logout(&info.login_id, token.as_str())
426                        .with_login_type(&info.login_type)
427                }
428                LogoutMode::KickOut => {
429                    SaTokenEvent::kick_out(&info.login_id, token.as_str())
430                        .with_login_type(&info.login_type)
431                }
432                LogoutMode::Replaced => {
433                    SaTokenEvent::replaced(&info.login_id, token.as_str())
434                        .with_login_type(&info.login_type)
435                }
436            };
437            self.event_bus.publish(event).await;
438        } else if let Some(id) = login_id {
439            let event = match mode {
440                LogoutMode::Logout => SaTokenEvent::logout(&id, token.as_str()),
441                LogoutMode::KickOut => SaTokenEvent::kick_out(&id, token.as_str()),
442                LogoutMode::Replaced => SaTokenEvent::replaced(&id, token.as_str()),
443            };
444            self.event_bus.publish(event).await;
445        }
446
447        Ok(())
448    }
449    
450    /// 根据登录 ID 登出所有 token
451    pub async fn logout_by_login_id(&self, login_id: &str) -> SaTokenResult<()> {
452        // 优先使用多设备索引精确登出,避免依赖 keys 全表扫描
453        let idx_key = self.config.make_key("login:tokens:", login_id);
454        let tokens = self.load_string_list(&idx_key).await.unwrap_or_default();
455        if !tokens.is_empty() {
456            for t in tokens {
457                let _ = self.logout(&TokenValue::new(t)).await;
458            }
459            return Ok(());
460        }
461
462        // 回退:全量扫描 token 键(依赖 storage.keys,Redis 需实现 keys)
463        let token_prefix = format!("{}token:", self.config.key_prefix());
464        
465        if let Ok(keys) = self.storage.keys(&format!("{}*", token_prefix)).await {
466            for key in keys {
467                if let Ok(Some(token_info_str)) = self.storage.get(&key).await {
468                    if let Ok(token_info) = serde_json::from_str::<TokenInfo>(&token_info_str) {
469                        let ti_ns = self.account_ns(&token_info.login_type, &token_info.login_id);
470                        if ti_ns == login_id {
471                            let token_str = key[token_prefix.len()..].to_string();
472                            let _ = self.logout(&TokenValue::new(token_str)).await;
473                        }
474                    }
475                }
476            }
477        }
478        
479        Ok(())
480    }
481    
482    /// 获取 token 信息
483    pub async fn get_token_info(&self, token: &TokenValue) -> SaTokenResult<TokenInfo> {
484        if let Some(mapped) = self.get_token_id_mapping(token.as_str()).await? {
485            if is_kick_out_marker(&mapped) {
486                return Err(SaTokenError::AccountKickedOut);
487            }
488            if is_replaced_marker(&mapped) {
489                return Err(SaTokenError::AccountReplaced);
490            }
491        }
492
493        let key = self.config.make_key("token:", token.as_str());
494        let value = self.storage.get(&key).await
495            .map_err(|e| SaTokenError::StorageError(e.to_string()))?
496            .ok_or(SaTokenError::TokenNotFound)?;
497        
498        let token_info: TokenInfo = serde_json::from_str(&value)
499            .map_err(SaTokenError::SerializationError)?;
500        
501        // 检查是否过期
502        if token_info.is_expired() {
503            self.logout(token).await?;
504            return Err(SaTokenError::TokenExpired);
505        }
506
507        // 活跃超时冻结:超过 active_timeout 未活跃则拒绝(对齐 Java checkActiveTimeout)
508        if token_info.is_freeze(self.config.active_timeout) {
509            return Err(SaTokenError::TokenInactive);
510        }
511        
512        // 自动续签:刷新 last_active_time 并延长 token TTL(对齐 Java updateLastActiveToNow + autoRenew)
513        if self.config.auto_renew {
514            let renew_timeout = if self.config.active_timeout > 0 {
515                self.config.active_timeout
516            } else {
517                self.config.timeout
518            };
519
520            let mut renewed = token_info.clone();
521            renewed.update_active_time();
522            if renew_timeout > 0 {
523                renewed.expire_time =
524                    Some(Utc::now() + Duration::seconds(renew_timeout));
525            }
526
527            let key = self.config.make_key("token:", token.as_str());
528            if let Ok(value) = serde_json::to_string(&renewed) {
529                let storage_ttl = if renew_timeout > 0 {
530                    Some(std::time::Duration::from_secs(renew_timeout as u64))
531                } else {
532                    self.config.timeout_duration()
533                };
534                let _ = self.storage.set(&key, &value, storage_ttl).await;
535            }
536            return Ok(renewed);
537        }
538        
539        Ok(token_info)
540    }
541    
542    /// 检查 token 是否有效
543    pub async fn is_valid(&self, token: &TokenValue) -> bool {
544        self.get_token_info(token).await.is_ok()
545    }
546    
547    /// 获取 session
548    pub async fn get_session(&self, login_id: &str) -> SaTokenResult<SaSession> {
549        let key = self.config.make_key("session:", login_id);
550        let value = self.storage.get(&key).await
551            .map_err(|e| SaTokenError::StorageError(e.to_string()))?;
552        
553        if let Some(value) = value {
554            let session: SaSession = serde_json::from_str(&value)
555                .map_err(SaTokenError::SerializationError)?;
556            Ok(session)
557        } else {
558            Ok(SaSession::new(login_id))
559        }
560    }
561    
562    /// 保存 session
563    pub async fn save_session(&self, session: &SaSession) -> SaTokenResult<()> {
564        let key = self.config.make_key("session:", &session.id);
565        let value = serde_json::to_string(session)
566            .map_err(SaTokenError::SerializationError)?;
567        
568        self.storage.set(&key, &value, None).await
569            .map_err(|e| SaTokenError::StorageError(e.to_string()))?;
570        
571        Ok(())
572    }
573    
574    /// 删除 session
575    pub async fn delete_session(&self, login_id: &str) -> SaTokenResult<()> {
576        let key = self.config.make_key("session:", login_id);
577        self.storage.delete(&key).await
578            .map_err(|e| SaTokenError::StorageError(e.to_string()))?;
579        Ok(())
580    }
581    
582    /// 续期 token(重置过期时间)
583    pub async fn renew_timeout(
584        &self,
585        token: &TokenValue,
586        timeout_seconds: i64,
587    ) -> SaTokenResult<()> {
588        let token_info = self.get_token_info(token).await?;
589        self.renew_timeout_internal(token, timeout_seconds, &token_info).await
590    }
591    
592    /// 内部续期方法(避免递归调用 get_token_info)
593    async fn renew_timeout_internal(
594        &self,
595        token: &TokenValue,
596        timeout_seconds: i64,
597        token_info: &TokenInfo,
598    ) -> SaTokenResult<()> {
599        let mut new_token_info = token_info.clone();
600        
601        // 设置新的过期时间
602        use chrono::{Utc, Duration};
603        let new_expire_time = Utc::now() + Duration::seconds(timeout_seconds);
604        new_token_info.expire_time = Some(new_expire_time);
605        
606        // 保存更新后的 token 信息
607        let key = self.config.make_key("token:", token.as_str());
608        let value = serde_json::to_string(&new_token_info)
609            .map_err(SaTokenError::SerializationError)?;
610        
611        let timeout = std::time::Duration::from_secs(timeout_seconds as u64);
612        self.storage.set(&key, &value, Some(timeout)).await
613            .map_err(|e| SaTokenError::StorageError(e.to_string()))?;
614        
615        Ok(())
616    }
617    
618    /// 踢人下线(按 login_id,对该账号所有 token 执行 KICKOUT)
619    pub async fn kick_out(&self, login_id: &str) -> SaTokenResult<()> {
620        if let Some(online_mgr) = &self.online_manager {
621            let _ = online_mgr
622                .kick_out_notify(login_id, "Account kicked out".to_string())
623                .await;
624        }
625
626        let idx_key = self.config.make_key("login:tokens:", login_id);
627        let tokens = self.load_string_list(&idx_key).await.unwrap_or_default();
628        if !tokens.is_empty() {
629            for t in tokens {
630                self.kick_out_by_token(&TokenValue::new(t)).await?;
631            }
632        } else if let Ok(Some(token_str)) = self
633            .storage
634            .get(&self.config.make_key("login:token:", login_id))
635            .await
636        {
637            self.kick_out_by_token(&TokenValue::new(token_str)).await?;
638        }
639
640        self.delete_session(login_id).await?;
641        Ok(())
642    }
643
644    fn token_id_mapping_key(&self, token: &str) -> String {
645        self.config.make_key("token-id:", token)
646    }
647
648    async fn save_token_id_mapping(&self, token: &str, login_id: &str) -> SaTokenResult<()> {
649        self.storage
650            .set(
651                &self.token_id_mapping_key(token),
652                login_id,
653                self.config.timeout_duration(),
654            )
655            .await
656            .map_err(|e| SaTokenError::StorageError(e.to_string()))
657    }
658
659    async fn update_token_id_mapping(&self, token: &str, value: &str) -> SaTokenResult<()> {
660        self.storage
661            .set(&self.token_id_mapping_key(token), value, None)
662            .await
663            .map_err(|e| SaTokenError::StorageError(e.to_string()))
664    }
665
666    async fn delete_token_id_mapping(&self, token: &str) -> SaTokenResult<()> {
667        self.storage
668            .delete(&self.token_id_mapping_key(token))
669            .await
670            .map_err(|e| SaTokenError::StorageError(e.to_string()))
671    }
672
673    async fn get_token_id_mapping(&self, token: &str) -> SaTokenResult<Option<String>> {
674        self.storage
675            .get(&self.token_id_mapping_key(token))
676            .await
677            .map_err(|e| SaTokenError::StorageError(e.to_string()))
678    }
679
680    async fn enforce_max_login_count(&self, login_id: &str) -> SaTokenResult<()> {
681        if self.config.max_login_count <= 0 || !self.config.is_concurrent {
682            return Ok(());
683        }
684        let idx_key = self.config.make_key("login:tokens:", login_id);
685        loop {
686            let list = self.load_string_list(&idx_key).await?;
687            if list.len() as i64 <= self.config.max_login_count {
688                break;
689            }
690            let Some(oldest) = list.first().cloned() else {
691                break;
692            };
693            let mut trimmed = list;
694            trimmed.remove(0);
695            self.save_string_list(&idx_key, &trimmed).await?;
696            let token = TokenValue::new(oldest);
697            match self.config.overflow_logout_mode {
698                LogoutMode::Logout => self.logout(&token).await?,
699                LogoutMode::KickOut => self.kick_out_by_token(&token).await?,
700                LogoutMode::Replaced => self.replaced_by_token(&token).await?,
701            }
702        }
703        Ok(())
704    }
705
706    /// 账号命名空间:将 (login_type, login_id) 转为存储键的 id 段。
707    ///
708    /// - default/""/"login" → 返回 login_id 本身(兼容历史键)
709    /// - 其它 → 返回 "{login_type}:{login_id}"(多账号隔离)
710    pub(crate) fn account_ns(&self, login_type: &str, login_id: &str) -> String {
711        if login_type.is_empty() || login_type == "default" || login_type == "login" {
712            login_id.to_string()
713        } else {
714            format!("{}:{}", login_type, login_id)
715        }
716    }
717
718    /// 构造 login_id -> token 映射键(区分 default 与带 login_type 的键)
719    fn login_token_mapping_key(&self, login_id: &str, login_type: &str) -> String {
720        let ns = self.account_ns(login_type, login_id);
721        self.config.make_key("login:token:", &ns)
722    }
723
724    /// 获取指定账号已登录设备终端列表
725    pub async fn get_terminal_list(
726        &self,
727        login_type: &str,
728        login_id: &str,
729        device_type: Option<&str>,
730    ) -> SaTokenResult<Vec<crate::session::SaTerminalInfo>> {
731        let ns = self.account_ns(login_type, login_id);
732        let session = self.get_session(&ns).await?;
733        Ok(session.get_terminal_list_by_device_type(device_type))
734    }
735
736    /// 获取指定账号的 token 列表(来自终端列表)
737    pub async fn get_token_value_list_by_login_id(
738        &self,
739        login_type: &str,
740        login_id: &str,
741        device_type: Option<&str>,
742    ) -> SaTokenResult<Vec<String>> {
743        let ns = self.account_ns(login_type, login_id);
744        let session = self.get_session(&ns).await?;
745        Ok(session.get_token_value_list_by_device_type(device_type))
746    }
747
748    /// 按 token 反查终端信息
749    pub async fn get_terminal_info_by_token(
750        &self,
751        token: &TokenValue,
752    ) -> SaTokenResult<Option<crate::session::SaTerminalInfo>> {
753        let info = match self.get_token_info(token).await {
754            Ok(i) => i,
755            Err(_) => return Ok(None),
756        };
757        let ns = self.account_ns(&info.login_type, &info.login_id);
758        let session = self.get_session(&ns).await?;
759        Ok(session.get_terminal(token.as_str()).cloned())
760    }
761
762    /// 追加 token 到多设备列表 login:tokens:{login_id}(去重)
763    async fn append_token_index(&self, login_id: &str, token: &str) -> SaTokenResult<()> {
764        let key = self.config.make_key("login:tokens:", login_id);
765        let mut list = self.load_string_list(&key).await?;
766        if !list.iter().any(|t| t == token) {
767            list.push(token.to_string());
768            self.save_string_list(&key, &list).await?;
769        }
770        Ok(())
771    }
772
773    /// 从多设备列表移除某个 token(logout 时调用)
774    async fn remove_token_index(&self, login_id: &str, token: &str) -> SaTokenResult<()> {
775        let key = self.config.make_key("login:tokens:", login_id);
776        let mut list = self.load_string_list(&key).await?;
777        let before = list.len();
778        list.retain(|t| t != token);
779        if list.len() != before {
780            self.save_string_list(&key, &list).await?;
781        }
782        Ok(())
783    }
784}
785
786// ==================== 权限 / 角色持久化(基于 SaStorage) ====================
787
788impl SaTokenManager {
789    /// 构造权限存储键:{prefix}permission:{login_id}
790    fn permission_key(&self, login_id: &str) -> String {
791        self.config.make_key("permission:", login_id)
792    }
793
794    /// 构造角色存储键:{prefix}role:{login_id}
795    fn role_key(&self, login_id: &str) -> String {
796        self.config.make_key("role:", login_id)
797    }
798
799    fn permission_key_ns(&self, login_type: &str, login_id: &str) -> String {
800        let ns = self.account_ns(login_type, login_id);
801        self.config.make_key("permission:", &ns)
802    }
803
804    fn role_key_ns(&self, login_type: &str, login_id: &str) -> String {
805        let ns = self.account_ns(login_type, login_id);
806        self.config.make_key("role:", &ns)
807    }
808
809    pub async fn get_permissions_with_type(
810        &self,
811        login_type: &str,
812        login_id: &str,
813    ) -> SaTokenResult<Vec<String>> {
814        if let Some(iface) = &self.stp_interface {
815            return iface.get_permission_list(login_id, login_type).await;
816        }
817        self.load_string_list(&self.permission_key_ns(login_type, login_id))
818            .await
819    }
820
821    pub async fn set_permissions_with_type(
822        &self,
823        login_type: &str,
824        login_id: &str,
825        permissions: Vec<String>,
826    ) -> SaTokenResult<()> {
827        self.save_string_list(
828            &self.permission_key_ns(login_type, login_id),
829            &permissions,
830        )
831        .await
832    }
833
834    pub async fn get_roles_with_type(
835        &self,
836        login_type: &str,
837        login_id: &str,
838    ) -> SaTokenResult<Vec<String>> {
839        if let Some(iface) = &self.stp_interface {
840            return iface.get_role_list(login_id, login_type).await;
841        }
842        self.load_string_list(&self.role_key_ns(login_type, login_id))
843            .await
844    }
845
846    pub async fn set_roles_with_type(
847        &self,
848        login_type: &str,
849        login_id: &str,
850        roles: Vec<String>,
851    ) -> SaTokenResult<()> {
852        self.save_string_list(&self.role_key_ns(login_type, login_id), &roles)
853            .await
854    }
855
856    /// 将字符串列表序列化为 JSON 并写入存储
857    /// 权限/角色无过期需求,TTL 固定使用 None(永久保存)
858    async fn save_string_list(&self, key: &str, list: &[String]) -> SaTokenResult<()> {
859        let value = serde_json::to_string(list).map_err(SaTokenError::SerializationError)?;
860        self.storage
861            .set(key, &value, None)
862            .await
863            .map_err(|e| SaTokenError::StorageError(e.to_string()))
864    }
865
866    /// 从存储读取字符串列表
867    /// 键不存在时返回空 Vec(视为该用户无任何权限/角色)
868    async fn load_string_list(&self, key: &str) -> SaTokenResult<Vec<String>> {
869        match self
870            .storage
871            .get(key)
872            .await
873            .map_err(|e| SaTokenError::StorageError(e.to_string()))?
874        {
875            Some(value) => serde_json::from_str(&value).map_err(SaTokenError::SerializationError),
876            None => Ok(Vec::new()),
877        }
878    }
879
880    /// 覆盖设置用户权限列表
881    /// 会完全替换该用户的所有权限
882    pub async fn set_permissions(&self, login_id: &str, permissions: Vec<String>) -> SaTokenResult<()> {
883        self.save_string_list(&self.permission_key(login_id), &permissions).await
884    }
885
886    /// 获取用户全部权限列表
887    /// 用户不存在或无权限时返回空列表
888    pub async fn get_permissions(&self, login_id: &str) -> SaTokenResult<Vec<String>> {
889        if let Some(iface) = &self.stp_interface {
890            return iface.get_permission_list(login_id, "default").await;
891        }
892        self.load_string_list(&self.permission_key(login_id)).await
893    }
894
895    /// 追加单个权限(已存在则跳过,避免重复)
896    /// 采用读-改-写模式,分布式高并发下存在竞态风险
897    pub async fn add_permission(&self, login_id: &str, permission: String) -> SaTokenResult<()> {
898        let key = self.permission_key(login_id);
899        let mut list = self.load_string_list(&key).await?;
900        if !list.contains(&permission) {
901            list.push(permission);
902            self.save_string_list(&key, &list).await?;
903        }
904        Ok(())
905    }
906
907    /// 移除用户的某个权限
908    /// 不存在时无操作,仅在确实删除了元素时才回写存储
909    pub async fn remove_permission(&self, login_id: &str, permission: &str) -> SaTokenResult<()> {
910        let key = self.permission_key(login_id);
911        let mut list = self.load_string_list(&key).await?;
912        let before = list.len();
913        list.retain(|p| p != permission);
914        if list.len() != before {
915            self.save_string_list(&key, &list).await?;
916        }
917        Ok(())
918    }
919
920    /// 清除用户的全部权限
921    /// 直接删除对应存储键
922    pub async fn clear_permissions(&self, login_id: &str) -> SaTokenResult<()> {
923        self.storage
924            .delete(&self.permission_key(login_id))
925            .await
926            .map_err(|e| SaTokenError::StorageError(e.to_string()))
927    }
928
929    /// 覆盖设置用户角色列表
930    /// 会完全替换该用户的所有角色
931    pub async fn set_roles(&self, login_id: &str, roles: Vec<String>) -> SaTokenResult<()> {
932        self.save_string_list(&self.role_key(login_id), &roles).await
933    }
934
935    /// 获取用户全部角色列表
936    /// 用户不存在或无角色时返回空列表
937    pub async fn get_roles(&self, login_id: &str) -> SaTokenResult<Vec<String>> {
938        if let Some(iface) = &self.stp_interface {
939            return iface.get_role_list(login_id, "default").await;
940        }
941        self.load_string_list(&self.role_key(login_id)).await
942    }
943
944    /// 追加单个角色(已存在则跳过,避免重复)
945    /// 采用读-改-写模式,分布式高并发下存在竞态风险
946    pub async fn add_role(&self, login_id: &str, role: String) -> SaTokenResult<()> {
947        let key = self.role_key(login_id);
948        let mut list = self.load_string_list(&key).await?;
949        if !list.contains(&role) {
950            list.push(role);
951            self.save_string_list(&key, &list).await?;
952        }
953        Ok(())
954    }
955
956    /// 移除用户的某个角色
957    /// 不存在时无操作,仅在确实删除了元素时才回写存储
958    pub async fn remove_role(&self, login_id: &str, role: &str) -> SaTokenResult<()> {
959        let key = self.role_key(login_id);
960        let mut list = self.load_string_list(&key).await?;
961        let before = list.len();
962        list.retain(|r| r != role);
963        if list.len() != before {
964            self.save_string_list(&key, &list).await?;
965        }
966        Ok(())
967    }
968
969    /// 清除用户的全部角色
970    /// 直接删除对应存储键
971    pub async fn clear_roles(&self, login_id: &str) -> SaTokenResult<()> {
972        self.storage
973            .delete(&self.role_key(login_id))
974            .await
975            .map_err(|e| SaTokenError::StorageError(e.to_string()))
976    }
977}
978
979#[cfg(test)]
980mod tests {
981    use super::*;
982    use sa_token_storage_memory::MemoryStorage;
983    use crate::config::{LogoutMode, TokenStyle};
984
985    fn make_manager(is_concurrent: bool, auto_renew: bool, active_timeout: i64) -> SaTokenManager {
986        let config = SaTokenConfig {
987            timeout: 3600,
988            token_style: TokenStyle::Uuid,
989            is_concurrent,
990            auto_renew,
991            active_timeout,
992            ..Default::default()
993        };
994        SaTokenManager::new(Arc::new(MemoryStorage::new()), config)
995    }
996
997    #[tokio::test]
998    async fn test_non_concurrent_login_invalidates_previous_token() {
999        let mgr = make_manager(false, false, -1);
1000        let t1 = mgr.login("user_1").await.unwrap();
1001        assert!(mgr.is_valid(&t1).await);
1002        let t2 = mgr.login("user_1").await.unwrap();
1003        assert!(!mgr.is_valid(&t1).await);
1004        assert!(mgr.is_valid(&t2).await);
1005    }
1006
1007    #[tokio::test]
1008    async fn test_logout_clears_login_token_mapping() {
1009        let mgr = make_manager(true, false, -1);
1010        let token = mgr.login("user_1").await.unwrap();
1011        let map_key = mgr.config.make_key("login:token:", "user_1");
1012        assert!(mgr.storage.get(&map_key).await.unwrap().is_some());
1013        mgr.logout(&token).await.unwrap();
1014        assert!(mgr.storage.get(&map_key).await.unwrap().is_none());
1015    }
1016
1017    #[tokio::test]
1018    async fn test_concurrent_login_appends_token_index() {
1019        let mgr = make_manager(true, false, -1);
1020        let t1 = mgr.login("user_1").await.unwrap();
1021        let t2 = mgr.login("user_1").await.unwrap();
1022        let idx_key = mgr.config.make_key("login:tokens:", "user_1");
1023        let list: Vec<String> = serde_json::from_str(
1024            &mgr.storage.get(&idx_key).await.unwrap().unwrap(),
1025        )
1026        .unwrap();
1027        assert_eq!(list.len(), 2);
1028        assert!(list.contains(&t1.as_str().to_string()));
1029        assert!(list.contains(&t2.as_str().to_string()));
1030    }
1031
1032    #[tokio::test]
1033    async fn test_active_timeout_freeze_returns_inactive() {
1034        let mgr = make_manager(true, false, 1);
1035        let token = mgr.login("user_1").await.unwrap();
1036        let key = mgr.config.make_key("token:", token.as_str());
1037        let mut info = mgr.get_token_info(&token).await.unwrap();
1038        info.last_active_time = Utc::now() - Duration::seconds(10);
1039        mgr.storage
1040            .set(
1041                &key,
1042                &serde_json::to_string(&info).unwrap(),
1043                mgr.config.timeout_duration(),
1044            )
1045            .await
1046            .unwrap();
1047        let result = mgr.get_token_info(&token).await;
1048        assert!(matches!(result, Err(SaTokenError::TokenInactive)));
1049    }
1050
1051    #[tokio::test]
1052    async fn test_auto_renew_updates_last_active_time() {
1053        let mgr = make_manager(true, true, 3600);
1054        let token = mgr.login("user_1").await.unwrap();
1055        let before = mgr.get_token_info(&token).await.unwrap().last_active_time;
1056        tokio::time::sleep(std::time::Duration::from_millis(20)).await;
1057        let after = mgr.get_token_info(&token).await.unwrap().last_active_time;
1058        assert!(after >= before);
1059    }
1060
1061    #[tokio::test]
1062    async fn test_login_with_nonce_when_enabled() {
1063        let config = SaTokenConfig {
1064            enable_nonce: true,
1065            nonce_timeout: 60,
1066            auto_renew: false,
1067            ..Default::default()
1068        };
1069        let mgr = SaTokenManager::new(Arc::new(MemoryStorage::new()), config);
1070        let nonce_mgr = crate::nonce::NonceManager::new(mgr.storage.clone(), 60);
1071        let nonce = nonce_mgr.generate();
1072        let token = mgr
1073            .login_with_options("user_1", None, None, None, Some(nonce.clone()), None)
1074            .await
1075            .unwrap();
1076        assert!(mgr.is_valid(&token).await);
1077        let result = mgr
1078            .login_with_options("user_1", None, None, None, Some(nonce), None)
1079            .await;
1080        assert!(matches!(result, Err(SaTokenError::NonceAlreadyUsed)));
1081    }
1082
1083    #[tokio::test]
1084    async fn test_kickout_token_returns_kicked_out() {
1085        let mgr = make_manager(true, false, -1);
1086        let token = mgr.login("user_kick").await.unwrap();
1087        mgr.kick_out_by_token(&token).await.unwrap();
1088        let err = mgr.get_token_info(&token).await.unwrap_err();
1089        assert!(matches!(err, SaTokenError::AccountKickedOut));
1090    }
1091
1092    #[tokio::test]
1093    async fn test_replaced_token_returns_replaced() {
1094        let mgr = make_manager(false, false, -1);
1095        let t1 = mgr.login("user_rep").await.unwrap();
1096        let _t2 = mgr.login("user_rep").await.unwrap();
1097        let err = mgr.get_token_info(&t1).await.unwrap_err();
1098        assert!(matches!(err, SaTokenError::AccountReplaced));
1099    }
1100
1101    #[tokio::test]
1102    async fn test_is_share_reuses_token() {
1103        let config = SaTokenConfig {
1104            is_share: true,
1105            is_concurrent: true,
1106            ..Default::default()
1107        };
1108        let mgr = SaTokenManager::new(Arc::new(MemoryStorage::new()), config);
1109        let t1 = mgr.login("user_share").await.unwrap();
1110        let t2 = mgr.login("user_share").await.unwrap();
1111        assert_eq!(t1.as_str(), t2.as_str());
1112    }
1113
1114    #[tokio::test]
1115    async fn test_max_login_count_overflow_kickout() {
1116        let config = SaTokenConfig {
1117            is_concurrent: true,
1118            max_login_count: 2,
1119            overflow_logout_mode: LogoutMode::KickOut,
1120            ..Default::default()
1121        };
1122        let mgr = SaTokenManager::new(Arc::new(MemoryStorage::new()), config);
1123        let t1 = mgr.login("user_max").await.unwrap();
1124        let _t2 = mgr.login("user_max").await.unwrap();
1125        let t3 = mgr.login("user_max").await.unwrap();
1126        assert!(matches!(
1127            mgr.get_token_info(&t1).await,
1128            Err(SaTokenError::AccountKickedOut)
1129        ));
1130        assert!(mgr.is_valid(&t3).await);
1131    }
1132
1133    #[test]
1134    fn test_account_ns_default_unchanged() {
1135        let mgr = make_manager(true, false, -1);
1136        assert_eq!(mgr.account_ns("default", "u1"), "u1");
1137        assert_eq!(mgr.account_ns("login", "u1"), "u1");
1138        assert_eq!(mgr.account_ns("", "u1"), "u1");
1139        assert_eq!(mgr.account_ns("admin", "u1"), "admin:u1");
1140    }
1141
1142    #[tokio::test]
1143    async fn test_login_writes_terminal_and_logout_removes() {
1144        let mgr = make_manager(true, false, -1);
1145        let token = mgr
1146            .login_with_options("u1", None, Some("PC".to_string()), None, None, None)
1147            .await
1148            .unwrap();
1149        let terminals = mgr.get_terminal_list("default", "u1", None).await.unwrap();
1150        assert_eq!(terminals.len(), 1);
1151        assert_eq!(terminals[0].token_value, token.as_str());
1152        assert_eq!(terminals[0].device_type, "PC");
1153        assert_eq!(terminals[0].index, 1);
1154
1155        mgr.logout(&token).await.unwrap();
1156        let terminals = mgr.get_terminal_list("default", "u1", None).await.unwrap();
1157        assert!(terminals.is_empty());
1158    }
1159
1160    #[tokio::test]
1161    async fn test_terminal_filter_by_device_type() {
1162        let mgr = make_manager(true, false, -1);
1163        mgr.login_with_options("u1", None, Some("PC".to_string()), None, None, None)
1164            .await
1165            .unwrap();
1166        mgr.login_with_options("u1", None, Some("APP".to_string()), None, None, None)
1167            .await
1168            .unwrap();
1169        assert_eq!(
1170            mgr.get_terminal_list("default", "u1", Some("PC"))
1171                .await
1172                .unwrap()
1173                .len(),
1174            1
1175        );
1176        assert_eq!(
1177            mgr.get_token_value_list_by_login_id("default", "u1", None)
1178                .await
1179                .unwrap()
1180                .len(),
1181            2
1182        );
1183    }
1184
1185    #[tokio::test]
1186    async fn test_permissions_isolated_by_login_type() {
1187        let mgr = make_manager(true, false, -1);
1188        mgr.set_permissions_with_type("admin", "u1", vec!["a:read".to_string()])
1189            .await
1190            .unwrap();
1191        mgr.set_permissions_with_type("user", "u1", vec!["u:read".to_string()])
1192            .await
1193            .unwrap();
1194        let admin_perms = mgr.get_permissions_with_type("admin", "u1").await.unwrap();
1195        let user_perms = mgr.get_permissions_with_type("user", "u1").await.unwrap();
1196        assert_eq!(admin_perms, vec!["a:read".to_string()]);
1197        assert_eq!(user_perms, vec!["u:read".to_string()]);
1198    }
1199}