Skip to main content

sa_token_core/
config.rs

1// Author: 金书记
2//
3//! 配置模块
4
5use std::time::Duration;
6use std::sync::Arc;
7use serde::{Deserialize, Serialize};
8use sa_token_adapter::storage::SaStorage;
9use crate::event::SaTokenListener;
10
11/// sa-token 配置
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SaTokenConfig {
14    /// Token 名称(例如在 header 或 cookie 中的键名)
15    pub token_name: String,
16    
17    /// Token 有效期(秒),-1 表示永久有效
18    pub timeout: i64,
19    
20    /// Token 最低活跃频率(秒),-1 表示不限制
21    ///
22    /// 超过该间隔未活跃则 token 冻结(`TokenInactive`);配合 `auto_renew` 时亦用于续签时长。
23    pub active_timeout: i64,
24
25    /// 是否启用 per-token 动态 active_timeout(对齐 Java dynamicActiveTimeout,Phase2 完善)
26    pub dynamic_active_timeout: bool,
27    
28    /// 是否开启自动续签(默认 true,对齐 Java SaTokenConfig)
29    /// 
30    /// 如果设置为 true,在以下场景会自动续签 token:
31    /// - 调用 get_token_info() 时
32    /// - 中间件验证 token 时
33    /// - 调用无参数的 StpUtil 方法时
34    /// 
35    /// 续签时长由 active_timeout 决定:
36    /// - 如果 active_timeout > 0,则续签 active_timeout 秒
37    /// - 如果 active_timeout <= 0,则续签 timeout 秒
38    pub auto_renew: bool,
39    
40    /// 是否允许同一账号并发登录
41    pub is_concurrent: bool,
42    
43    /// 在多人登录同一账号时,是否共享一个 token(默认 false,对齐 Java)
44    pub is_share: bool,
45    
46    /// Token 风格(uuid、simple-uuid、random-32、random-64、random-128)
47    pub token_style: TokenStyle,
48    
49    /// 是否输出操作日志
50    pub is_log: bool,
51    
52    /// 是否从 cookie 中读取 token
53    pub is_read_cookie: bool,
54    
55    /// 是否从 header 中读取 token
56    pub is_read_header: bool,
57    
58    /// 是否从请求体中读取 token
59    pub is_read_body: bool,
60    
61    /// JWT 密钥(如果使用 JWT)
62    pub jwt_secret_key: Option<String>,
63    
64    /// JWT 算法(默认 HS256)
65    pub jwt_algorithm: Option<String>,
66    
67    /// JWT 签发者
68    pub jwt_issuer: Option<String>,
69    
70    /// JWT 受众
71    pub jwt_audience: Option<String>,
72
73    /// JWT 生成失败时是否回退为 UUID(默认 true);失败时始终 `tracing::warn`
74    pub jwt_fallback_on_error: bool,
75    
76    /// 是否启用防重放攻击(nonce 机制)
77    pub enable_nonce: bool,
78    
79    /// Nonce 有效期(秒),-1 表示使用 token timeout
80    pub nonce_timeout: i64,
81    
82    /// 是否启用 Refresh Token
83    pub enable_refresh_token: bool,
84    
85    /// Refresh Token 有效期(秒),默认 7 天
86    pub refresh_token_timeout: i64,
87
88    /// 存储键前缀(用于 Redis/数据库等存储后端的键命名)
89    /// 默认 "sa:",所有存储键将以此为前缀,如 "sa:token:"、"sa:session:" 等
90    /// 注意:此字段与 token_prefix(HTTP header 中的 Bearer 前缀)不同
91    pub storage_key_prefix: String,
92
93    /// 同一账号最大登录数量,-1 表示不限制
94    pub max_login_count: i64,
95
96    /// 超出 max_login_count 时的下线模式
97    pub overflow_logout_mode: LogoutMode,
98
99    /// 非并发顶号时:踢旧设备还是拒绝新登录
100    pub replaced_login_exit_mode: ReplacedLoginExitMode,
101
102    /// 顶号范围:当前设备类型或全部设备
103    pub replaced_range: ReplacedRange,
104
105    /// 登录时是否立即创建 Token-Session
106    pub right_now_create_token_session: bool,
107
108    /// 获取 Token-Session 时是否校验 token 登录态
109    pub token_session_check_login: bool,
110
111    /// 默认 logout 范围(预留)
112    pub logout_range: LogoutRange,
113
114    /// logout 时是否保留 Token-Session
115    pub is_logout_keep_token_session: bool,
116}
117
118impl Default for SaTokenConfig {
119    fn default() -> Self {
120        Self {
121            token_name: "sa-token".to_string(),
122            timeout: 2592000, // 30天
123            active_timeout: -1,
124            dynamic_active_timeout: false,
125            auto_renew: true,
126            is_concurrent: true,
127            is_share: false,
128            token_style: TokenStyle::Uuid,
129            is_log: false,
130            is_read_cookie: true,
131            is_read_header: true,
132            is_read_body: true,
133            jwt_secret_key: None,
134            jwt_algorithm: Some("HS256".to_string()),
135            jwt_issuer: None,
136            jwt_audience: None,
137            jwt_fallback_on_error: true,
138            enable_nonce: false,
139            nonce_timeout: -1,
140            enable_refresh_token: false,
141            refresh_token_timeout: 604800, // 7 天
142            storage_key_prefix: "sa:".to_string(),
143            max_login_count: -1,
144            overflow_logout_mode: LogoutMode::Logout,
145            replaced_login_exit_mode: ReplacedLoginExitMode::OldDevice,
146            replaced_range: ReplacedRange::CurrDeviceType,
147            right_now_create_token_session: false,
148            token_session_check_login: true,
149            logout_range: LogoutRange::Token,
150            is_logout_keep_token_session: false,
151        }
152    }
153}
154
155impl SaTokenConfig {
156    pub fn builder() -> SaTokenConfigBuilder {
157        SaTokenConfigBuilder::default()
158    }
159    
160    pub fn timeout_duration(&self) -> Option<Duration> {
161        if self.timeout < 0 {
162            None
163        } else {
164            Some(Duration::from_secs(self.timeout as u64))
165        }
166    }
167
168    /// 构造存储键:拼接 storage_key_prefix 与后缀
169    /// 例如:make_key("token:", "abc123") → "sa:token:abc123"
170    pub fn make_key(&self, suffix: &str, id: &str) -> String {
171        format!("{}{}{}", self.storage_key_prefix, suffix, id)
172    }
173
174    /// 获取存储键前缀
175    pub fn key_prefix(&self) -> &str {
176        &self.storage_key_prefix
177    }
178}
179
180/// Token 风格 | Token Style
181#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
182pub enum TokenStyle {
183    /// UUID 风格 | UUID style
184    Uuid,
185    /// 简化的 UUID(去掉横杠)| Simple UUID (without hyphens)
186    SimpleUuid,
187    /// 32位随机字符串 | 32-character random string
188    Random32,
189    /// 64位随机字符串 | 64-character random string
190    Random64,
191    /// 128位随机字符串 | 128-character random string
192    Random128,
193    /// JWT 风格(JSON Web Token)| JWT style (JSON Web Token)
194    Jwt,
195    /// Hash 风格(SHA256哈希)| Hash style (SHA256 hash)
196    Hash,
197    /// 时间戳风格(毫秒级时间戳+随机数)| Timestamp style (millisecond timestamp + random)
198    Timestamp,
199    /// Tik 风格(短小精悍的8位字符)| Tik style (short 8-character token)
200    Tik,
201}
202
203/// 下线模式(对齐 Java SaLogoutMode)
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
205pub enum LogoutMode {
206    #[default]
207    Logout,
208    KickOut,
209    Replaced,
210}
211
212/// 非并发顶号时踢旧或拒新
213#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
214pub enum ReplacedLoginExitMode {
215    #[default]
216    OldDevice,
217    NewDevice,
218}
219
220/// 顶号影响范围
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
222pub enum ReplacedRange {
223    #[default]
224    CurrDeviceType,
225    AllDeviceType,
226}
227
228/// logout 范围(预留)
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
230pub enum LogoutRange {
231    #[default]
232    Token,
233    Account,
234}
235
236/// 配置构建器
237#[derive(Default)]
238pub struct SaTokenConfigBuilder {
239    config: SaTokenConfig,
240    storage: Option<Arc<dyn SaStorage>>,
241    listeners: Vec<Arc<dyn SaTokenListener>>,
242}
243
244
245impl SaTokenConfigBuilder {
246    pub fn token_name(mut self, name: impl Into<String>) -> Self {
247        self.config.token_name = name.into();
248        self
249    }
250    
251    pub fn timeout(mut self, timeout: i64) -> Self {
252        self.config.timeout = timeout;
253        self
254    }
255    
256    pub fn active_timeout(mut self, timeout: i64) -> Self {
257        self.config.active_timeout = timeout;
258        self
259    }
260
261    /// 设置是否启用 per-token 动态 active_timeout
262    pub fn dynamic_active_timeout(mut self, enabled: bool) -> Self {
263        self.config.dynamic_active_timeout = enabled;
264        self
265    }
266    
267    /// 设置是否开启自动续签
268    pub fn auto_renew(mut self, enabled: bool) -> Self {
269        self.config.auto_renew = enabled;
270        self
271    }
272    
273    pub fn is_concurrent(mut self, concurrent: bool) -> Self {
274        self.config.is_concurrent = concurrent;
275        self
276    }
277    
278    pub fn is_share(mut self, share: bool) -> Self {
279        self.config.is_share = share;
280        self
281    }
282    
283    pub fn token_style(mut self, style: TokenStyle) -> Self {
284        self.config.token_style = style;
285        self
286    }
287
288
289    /// 设置存储键前缀(默认 "sa:")
290    ///
291    /// 注意:此字段与 token_prefix(HTTP header 中的 Bearer 前缀)不同
292    /// 此前缀用于 Redis/数据库等存储后端的键命名
293    pub fn storage_key_prefix(mut self, prefix: impl Into<String>) -> Self {
294        self.config.storage_key_prefix = prefix.into();
295        self
296    }
297    
298    pub fn jwt_secret_key(mut self, key: impl Into<String>) -> Self {
299        self.config.jwt_secret_key = Some(key.into());
300        self
301    }
302    
303    /// 设置 JWT 算法
304    pub fn jwt_algorithm(mut self, algorithm: impl Into<String>) -> Self {
305        self.config.jwt_algorithm = Some(algorithm.into());
306        self
307    }
308    
309    /// 设置 JWT 签发者
310    pub fn jwt_issuer(mut self, issuer: impl Into<String>) -> Self {
311        self.config.jwt_issuer = Some(issuer.into());
312        self
313    }
314    
315    /// 设置 JWT 受众
316    pub fn jwt_audience(mut self, audience: impl Into<String>) -> Self {
317        self.config.jwt_audience = Some(audience.into());
318        self
319    }
320
321    pub fn jwt_fallback_on_error(mut self, fallback: bool) -> Self {
322        self.config.jwt_fallback_on_error = fallback;
323        self
324    }
325    
326    /// 启用防重放攻击(nonce 机制)
327    pub fn enable_nonce(mut self, enable: bool) -> Self {
328        self.config.enable_nonce = enable;
329        self
330    }
331    
332    /// 设置 Nonce 有效期(秒)
333    pub fn nonce_timeout(mut self, timeout: i64) -> Self {
334        self.config.nonce_timeout = timeout;
335        self
336    }
337    
338    /// 启用 Refresh Token
339    pub fn enable_refresh_token(mut self, enable: bool) -> Self {
340        self.config.enable_refresh_token = enable;
341        self
342    }
343    
344    /// 设置 Refresh Token 有效期(秒)
345    pub fn refresh_token_timeout(mut self, timeout: i64) -> Self {
346        self.config.refresh_token_timeout = timeout;
347        self
348    }
349
350    pub fn max_login_count(mut self, count: i64) -> Self {
351        self.config.max_login_count = count;
352        self
353    }
354
355    pub fn overflow_logout_mode(mut self, mode: LogoutMode) -> Self {
356        self.config.overflow_logout_mode = mode;
357        self
358    }
359
360    pub fn replaced_login_exit_mode(mut self, mode: ReplacedLoginExitMode) -> Self {
361        self.config.replaced_login_exit_mode = mode;
362        self
363    }
364
365    pub fn replaced_range(mut self, range: ReplacedRange) -> Self {
366        self.config.replaced_range = range;
367        self
368    }
369
370    pub fn right_now_create_token_session(mut self, enabled: bool) -> Self {
371        self.config.right_now_create_token_session = enabled;
372        self
373    }
374
375    pub fn token_session_check_login(mut self, enabled: bool) -> Self {
376        self.config.token_session_check_login = enabled;
377        self
378    }
379
380    pub fn logout_range(mut self, range: LogoutRange) -> Self {
381        self.config.logout_range = range;
382        self
383    }
384
385    pub fn is_logout_keep_token_session(mut self, keep: bool) -> Self {
386        self.config.is_logout_keep_token_session = keep;
387        self
388    }
389    
390    /// 设置存储方式
391    pub fn storage(mut self, storage: Arc<dyn SaStorage>) -> Self {
392        self.storage = Some(storage);
393        self
394    }
395    
396    /// 注册事件监听器
397    /// 
398    /// 可以多次调用以注册多个监听器
399    /// 
400    /// # 示例
401    /// ```rust,ignore
402    /// use std::sync::Arc;
403    /// use sa_token_core::{SaTokenConfig, SaTokenListener};
404    /// 
405    /// struct MyListener;
406    /// impl SaTokenListener for MyListener { /* ... */ }
407    /// 
408    /// let manager = SaTokenConfig::builder()
409    ///     .storage(Arc::new(MemoryStorage::new()))
410    ///     .register_listener(Arc::new(MyListener))
411    ///     .build();
412    /// ```
413    pub fn register_listener(mut self, listener: Arc<dyn SaTokenListener>) -> Self {
414        self.listeners.push(listener);
415        self
416    }
417    
418    /// 构建 SaTokenManager(需要先设置 storage)
419    /// 
420    /// 自动完成以下操作:
421    /// 1. 创建 SaTokenManager
422    /// 2. 注册所有事件监听器
423    /// 3. 初始化 StpUtil
424    /// 
425    /// Auto-complete the following operations:
426    /// 1. Create SaTokenManager
427    /// 2. Register all event listeners
428    /// 3. Initialize StpUtil
429    /// 
430    /// # Panics
431    /// 如果未设置 storage,会 panic
432    /// 
433    /// # 示例
434    /// ```rust,ignore
435    /// use std::sync::Arc;
436    /// use sa_token_core::SaTokenConfig;
437    /// use sa_token_storage_memory::MemoryStorage;
438    /// 
439    /// // 一行代码完成所有初始化!
440    /// // Complete all initialization in one line!
441    /// SaTokenConfig::builder()
442    ///     .storage(Arc::new(MemoryStorage::new()))
443    ///     .timeout(7200)
444    ///     .register_listener(Arc::new(MyListener))
445    ///     .build();  // 自动初始化 StpUtil!
446    /// ```
447    pub fn build(self) -> crate::SaTokenManager {
448        let storage = self.storage.expect("Storage must be set before building SaTokenManager. Use .storage() method.");
449        let manager = crate::SaTokenManager::new(storage, self.config);
450        
451        // 同步注册所有监听器
452        // Register all listeners synchronously
453        if !self.listeners.is_empty() {
454            let event_bus = manager.event_bus();
455            for listener in self.listeners {
456                event_bus.register(listener);
457            }
458        }
459        
460        // 自动初始化 StpUtil
461        // Auto-initialize StpUtil
462        crate::StpUtil::init_manager(manager.clone());
463        
464        manager
465    }
466    
467    /// 仅构建配置(不创建 Manager)
468    pub fn build_config(self) -> SaTokenConfig {
469        self.config
470    }
471}