Skip to main content

sa_token_core/
config.rs

1// Author: 金书记
2//
3//! 配置模块 | Configuration module
4
5use crate::error::{SaTokenError, SaTokenResult};
6use crate::event::{SaTokenEventBus, SaTokenListener};
7use crate::keys::SaKeyLayout;
8use sa_token_adapter::serializer::SharedSerializer;
9use sa_token_adapter::storage::SaStorage;
10use serde::{Deserialize, Serialize, de::DeserializeOwned};
11use std::sync::Arc;
12use std::time::Duration;
13
14/// sa-token 全局配置。
15/// Global sa-token configuration.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct SaTokenConfig {
18    /// Token 名称(header / cookie / body 中的键名)
19    /// Token name (key used in header, cookie, or body)
20    pub token_name: String,
21
22    /// Token 有效期(秒),`-1` 表示永久有效
23    /// Token lifetime in seconds; `-1` means never expires
24    pub timeout: i64,
25
26    /// Token 最低活跃频率(秒),`-1` 表示不限制。
27    /// 超过该间隔未活跃则冻结(`TokenInactive`);开启 `auto_renew` 时亦用于续签时长。
28    ///
29    /// Minimum activity interval in seconds; `-1` disables the check.
30    /// Idle longer than this freezes the token (`TokenInactive`); also used as
31    /// the renewal window when `auto_renew` is enabled.
32    pub active_timeout: i64,
33
34    /// Per-token activity window override.
35    /// 是否启用逐 token 的活跃窗口覆盖。
36    pub dynamic_active_timeout: bool,
37
38    /// 是否开启自动续签(0.2.0 起默认 `false`,避免每次读 token 都写存储)
39    /// Whether to enable auto-renewal (defaults to `false` since 0.2.0 to avoid
40    /// a storage write on every token read)
41    pub auto_renew: bool,
42
43    /// 续签阈值(秒):仅当 token 剩余有效时间低于该值时才真正触发续签写入。
44    ///
45    /// 语义(三段):
46    /// - `< 0`(如 `-1`):不启用阈值,每次读取都续签 —— 兼容 0.1.x 旧行为
47    /// - `== 0`:仅当剩余时间 `<= 0`(已到期边界)才续签
48    /// - `> 0`:剩余时间 `<=` 阈值时才续签(推荐,默认 `300`)
49    ///
50    /// 注意:本字段仅在 `auto_renew == true` 时生效。
51    ///
52    /// Renewal threshold in seconds: a renewal write happens only when the
53    /// token's remaining lifetime drops below this value.
54    /// - `< 0`: threshold disabled, renew on every read (0.1.x behaviour)
55    /// - `== 0`: renew only when remaining lifetime `<= 0`
56    /// - `> 0`: renew when remaining lifetime `<=` threshold (recommended; default `300`)
57    ///
58    /// Only effective when `auto_renew == true`.
59    pub renew_threshold: i64,
60
61    /// 是否允许同一账号并发登录
62    /// Whether the same account may log in concurrently
63    pub is_concurrent: bool,
64
65    /// Concurrent logins share one token when true (default `false`).
66    /// 为 true 时同一账号并发登录共用一个 token(默认 `false`)。
67    pub is_share: bool,
68
69    /// Token 风格(uuid、simple-uuid、random-32、random-64、random-128 等)
70    /// Token generation style (uuid, simple-uuid, random-32/64/128, etc.)
71    pub token_style: TokenStyle,
72
73    /// 是否输出操作日志
74    /// Whether to emit operation logs
75    pub is_log: bool,
76
77    /// 是否从 cookie 中读取 token
78    /// Whether to read the token from cookies
79    pub is_read_cookie: bool,
80
81    /// 是否从 header 中读取 token
82    /// Whether to read the token from headers
83    pub is_read_header: bool,
84
85    /// 是否从请求体中读取 token
86    /// Whether to read the token from the request body
87    pub is_read_body: bool,
88
89    /// Optional token prefix. `None` = still strip a leading `Bearer `.
90    /// 可选 token 前缀。`None` 时仍剥离开头的 `Bearer `。
91    #[serde(default)]
92    pub token_prefix: Option<String>,
93
94    /// Cookie write settings (opt-in).
95    /// Cookie 下发配置(默认不写)。
96    #[serde(default)]
97    pub cookie: TokenCookieConfig,
98
99    /// JWT 密钥(使用 JWT 风格时)
100    /// JWT secret key (when using the JWT token style)
101    pub jwt_secret_key: Option<String>,
102
103    /// JWT 算法(默认 `HS256`)
104    /// JWT algorithm (default `HS256`)
105    pub jwt_algorithm: Option<String>,
106
107    /// JWT 签发者(`iss`)
108    /// JWT issuer (`iss`)
109    pub jwt_issuer: Option<String>,
110
111    /// JWT 受众(`aud`)
112    /// JWT audience (`aud`)
113    pub jwt_audience: Option<String>,
114
115    /// JWT 生成失败时是否回退为 UUID(默认 `false`);失败时始终记录日志
116    /// Whether to fall back to UUID when JWT generation fails (default `false`); always log on failure
117    pub jwt_fallback_on_error: bool,
118
119    /// 是否启用防重放攻击(nonce 机制)
120    /// Whether to enable anti-replay protection via nonce
121    pub enable_nonce: bool,
122
123    /// Nonce 有效期(秒),`-1` 表示沿用 token `timeout`
124    /// Nonce lifetime in seconds; `-1` follows token `timeout`
125    pub nonce_timeout: i64,
126
127    /// 是否启用 Refresh Token
128    /// Whether to enable refresh tokens
129    pub enable_refresh_token: bool,
130
131    /// Refresh Token 有效期(秒),默认 7 天(`604800`)
132    /// Refresh-token lifetime in seconds (default 7 days / `604800`)
133    pub refresh_token_timeout: i64,
134
135    /// 存储键前缀(Redis / 数据库等后端的键命名)。
136    /// 默认 `"sa:"`,所有逻辑键以此为前缀,如 `"sa:token:"`、`"sa:session:"`。
137    ///
138    /// Storage key prefix for Redis/DB backends.
139    /// Default `"sa:"`; all logical keys are prefixed, e.g. `"sa:token:"`, `"sa:session:"`.
140    pub storage_key_prefix: String,
141
142    /// 存储键布局策略(A3-1)
143    /// Storage key layout strategy (A3-1)
144    #[serde(default)]
145    pub key_layout: SaKeyLayout,
146
147    /// 同一账号最大登录数量,`-1` 表示不限制
148    /// Max concurrent logins per account; `-1` means unlimited
149    pub max_login_count: i64,
150
151    /// 超出 `max_login_count` 时的下线模式
152    /// Logout mode used when `max_login_count` is exceeded
153    pub overflow_logout_mode: LogoutMode,
154
155    /// 非并发顶号时:踢旧设备还是拒绝新登录
156    /// Non-concurrent replace policy: kick the old device or reject the new login
157    pub replaced_login_exit_mode: ReplacedLoginExitMode,
158
159    /// Replace scope on non-concurrent login (already enforced in AuthService).
160    /// 非并发顶号范围(AuthService 已落地)。
161    pub replaced_range: ReplacedRange,
162
163    /// 登录时是否立即创建 Token-Session
164    /// Whether to create a Token-Session immediately on login
165    pub right_now_create_token_session: bool,
166
167    /// 获取 Token-Session 时是否校验 token 登录态
168    /// Whether fetching a Token-Session requires a valid login
169    pub token_session_check_login: bool,
170
171    /// Default logout range: current token or entire account.
172    /// 默认 logout 范围:当前 token 或整个账号。
173    pub logout_range: LogoutRange,
174
175    /// logout 时是否保留 Token-Session
176    /// Whether to keep the Token-Session on logout
177    pub is_logout_keep_token_session: bool,
178
179    /// 权限/角色读缓存 TTL(秒)。`<= 0` 表示**关闭缓存**(默认),此时不分配任何缓存结构。
180    ///
181    /// 关闭是默认值的理由:多实例部署下缓存会带来「权限变更滞后」的安全窗口,
182    /// 必须由使用者显式权衡后开启,而不是默认埋一个隐患。
183    ///
184    /// TTL in seconds for the permission/role read cache. `<= 0` disables the
185    /// cache entirely (default) and allocates nothing. Disabled by default
186    /// because a multi-instance deployment would otherwise silently inherit a
187    /// staleness window for authorization decisions.
188    #[serde(default)]
189    pub grant_cache_ttl: i64,
190
191    /// 权限/角色缓存的**总条目上限**(跨全部分片)。达到上限时先清过期项,
192    /// 仍超限则淘汰「最早过期」的一项,保证内存有界。
193    ///
194    /// Global upper bound on cached entries across all shards. On overflow the
195    /// cache first drops expired entries, then evicts the soonest-to-expire
196    /// one, keeping memory bounded.
197    #[serde(default = "default_grant_cache_max_entries")]
198    pub grant_cache_max_entries: usize,
199
200    /// 是否启用**单飞**(single-flight):同一 key 并发未命中时只放行一次底层加载,
201    /// 其余请求等待复用结果,避免缓存击穿打爆外部数据源。
202    ///
203    /// Enables single-flight: concurrent misses on the same key trigger only one
204    /// underlying load, preventing a cache stampede against the data source.
205    #[serde(default = "default_true")]
206    pub grant_cache_single_flight: bool,
207
208    /// 是否启用**请求级授权快照**:同一请求(`SaTokenContext::scope`)内多次鉴权
209    /// 只读一次数据源。与 TTL 缓存不同,它随请求结束即销毁,**没有一致性窗口**,
210    /// 因此默认开启。
211    ///
212    /// Enables a per-request authorization snapshot so repeated checks inside one
213    /// `SaTokenContext::scope` hit the data source once. Unlike the TTL cache it
214    /// dies with the request, so there is no staleness window — hence on by default.
215    #[serde(default = "default_true")]
216    pub grant_request_scope: bool,
217
218    /// 注入只读 `StpInterface` 时的写策略,见 [`GrantWritePolicy`]。
219    /// Write policy when a read-only `StpInterface` is injected.
220    #[serde(default)]
221    pub grant_write_policy: GrantWritePolicy,
222
223    /// When true, role checks honour `*` wildcards (default `false` = exact).
224    /// 为 true 时角色校验识别 `*` 通配(默认 `false`,精确匹配)。
225    ///
226    /// Enabling routes roles through the same segment matcher used for permissions.
227    #[serde(default)]
228    pub role_wildcard: bool,
229
230    // ========== 上下文行为 | Context Behavior ==========
231    /// `with_current_mut` 在无上下文时是否自动创建空上下文(默认 false,返回 None)
232    ///
233    /// When `true`, `with_current_mut` auto-creates an empty context if none exists (fallback for
234    /// sync paths); when `false` (default), returns `None` to surface the programming error.
235    #[serde(default)]
236    pub context_auto_create: bool,
237
238    /// HTTP Basic account in `user:password` form. Empty = caller must pass account.
239    /// HTTP Basic 账号,格式 `user:password`。空表示调用方必须传入 account。
240    #[serde(default)]
241    pub http_basic: String,
242
243    /// Same-Token TTL in seconds; `<= 0` means no TTL (storage-dependent).
244    /// Same-Token 有效期(秒);`<= 0` 表示不设 TTL。
245    #[serde(default = "default_same_token_timeout")]
246    pub same_token_timeout: i64,
247
248    /// Header name for Same-Token.
249    /// Same-Token 请求头名。
250    #[serde(default = "default_same_token_header")]
251    pub same_token_header: String,
252
253    /// Max attempts when allocating a unique login / temp token. `-1` = do not retry.
254    /// 分配唯一登录/临时 token 的最大尝试次数。`-1` 表示不重试。
255    #[serde(default = "default_max_try_times")]
256    pub max_try_times: i32,
257
258    /// HMAC secret for `RequestSign` via StpUtil (independent from JWT).
259    /// StpUtil 使用的 HMAC 密钥(与 JWT 密钥分离)。
260    #[serde(default)]
261    pub sign_secret_key: Option<String>,
262
263    /// Timestamp window in seconds for `RequestSign` (default 300).
264    /// `RequestSign` 的时间窗(秒),默认 300。
265    #[serde(default = "default_sign_window_secs")]
266    pub sign_window_secs: i64,
267
268    /// 存储层序列化器(默认 JSON;可选 fory;不参与本结构的 serde 序列化)
269    /// Storage serializer (JSON by default; optional fory; skipped by this struct's serde)
270    #[serde(skip, default)]
271    pub serializer: SharedSerializer,
272}
273
274/// serde 默认值:缓存条目上限。4096 条 ≈ 4096 个活跃账号的权限列表。
275/// serde default for the cache capacity; 4096 active accounts is a safe baseline.
276fn default_grant_cache_max_entries() -> usize {
277    4096
278}
279
280/// serde 默认值:布尔真(serde 对 `bool` 的 `default` 是 `false`,需显式函数)
281/// serde default for `true`, since serde's `bool` default is `false`.
282fn default_true() -> bool {
283    true
284}
285
286fn default_same_token_timeout() -> i64 {
287    86400
288}
289
290fn default_same_token_header() -> String {
291    "SA-SAME-TOKEN".to_string()
292}
293
294fn default_max_try_times() -> i32 {
295    12
296}
297
298fn default_sign_window_secs() -> i64 {
299    300
300}
301
302fn default_true_cookie_http_only() -> bool {
303    true
304}
305
306/// Cookie attributes used when a handler opts into writing the token cookie.
307/// Handler 选择写入 token Cookie 时使用的属性。
308#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct TokenCookieConfig {
310    /// When false, `write_token_cookie` is a no-op (default).
311    /// 为 false 时 `write_token_cookie` 为空操作(默认)。
312    #[serde(default)]
313    pub is_write_cookie: bool,
314    /// `domain` | `domain`
315    pub domain: Option<String>,
316    /// `path` | `path`
317    pub path: Option<String>,
318    #[serde(default = "default_true_cookie_http_only")]
319    /// `http_only` | `http_only`
320    pub http_only: bool,
321    #[serde(default)]
322    /// `secure` | `secure`
323    pub secure: bool,
324    /// `same_site` | `same_site`
325    pub same_site: Option<sa_token_adapter::context::SameSite>,
326}
327
328impl Default for TokenCookieConfig {
329    fn default() -> Self {
330        Self {
331            is_write_cookie: false,
332            domain: None,
333            path: Some("/".into()),
334            http_only: true,
335            secure: false,
336            same_site: Some(sa_token_adapter::context::SameSite::Lax),
337        }
338    }
339}
340
341impl Default for SaTokenConfig {
342    fn default() -> Self {
343        Self {
344            token_name: "sa-token".to_string(),
345            timeout: 2592000, // 30 天 | 30 days
346            active_timeout: -1,
347            dynamic_active_timeout: false,
348            // B1:默认关闭自动续签,避免每次读 token 都写存储
349            // B1: auto-renew off by default to avoid a write on every token read
350            auto_renew: false,
351            renew_threshold: 300,
352            is_concurrent: true,
353            is_share: false,
354            token_style: TokenStyle::Uuid,
355            is_log: false,
356            is_read_cookie: true,
357            is_read_header: true,
358            is_read_body: true,
359            token_prefix: None,
360            cookie: TokenCookieConfig::default(),
361            jwt_secret_key: None,
362            jwt_algorithm: Some("HS256".to_string()),
363            jwt_issuer: None,
364            jwt_audience: None,
365            jwt_fallback_on_error: false, // 0.2.0:失败必须可见,禁止静默 UUID
366            enable_nonce: false,
367            nonce_timeout: -1,
368            enable_refresh_token: false,
369            refresh_token_timeout: 604800, // 7 天 | 7 days
370            storage_key_prefix: "sa:".to_string(),
371            key_layout: SaKeyLayout::ThreeSegment,
372            max_login_count: -1,
373            overflow_logout_mode: LogoutMode::Logout,
374            replaced_login_exit_mode: ReplacedLoginExitMode::OldDevice,
375            replaced_range: ReplacedRange::CurrDeviceType,
376            right_now_create_token_session: false,
377            token_session_check_login: true,
378            logout_range: LogoutRange::Token,
379            is_logout_keep_token_session: false,
380            grant_cache_ttl: 0,
381            grant_cache_max_entries: default_grant_cache_max_entries(),
382            grant_cache_single_flight: true,
383            grant_request_scope: true,
384            grant_write_policy: GrantWritePolicy::Warn,
385            role_wildcard: false,
386            context_auto_create: false,
387            http_basic: String::new(),
388            same_token_timeout: 86400,
389            same_token_header: "SA-SAME-TOKEN".to_string(),
390            max_try_times: 12,
391            sign_secret_key: None,
392            sign_window_secs: 300,
393            serializer: SharedSerializer::default(),
394        }
395    }
396}
397
398impl SaTokenConfig {
399    /// 创建配置构建器 | Create a configuration builder
400    pub fn builder() -> SaTokenConfigBuilder {
401        SaTokenConfigBuilder::default()
402    }
403
404    /// 将 `timeout` 转为 `Duration`;永久(`< 0`)时返回 `None`
405    /// Convert `timeout` to a `Duration`; returns `None` when permanent (`< 0`)
406    pub fn timeout_duration(&self) -> Option<Duration> {
407        if self.timeout < 0 {
408            None
409        } else {
410            Some(Duration::from_secs(self.timeout as u64))
411        }
412    }
413
414    /// Reject Jwt style without a usable secret. Call from builders.
415    /// Jwt 风格必须带可用密钥。由 builder 调用。
416    pub fn validate_jwt(&self) -> SaTokenResult<()> {
417        if matches!(self.token_style, TokenStyle::Jwt) {
418            match self.jwt_secret_key.as_deref() {
419                Some(s) if !s.trim().is_empty() => Ok(()),
420                _ => Err(SaTokenError::ConfigError(
421                    "jwt_secret_key required for Jwt token style".into(),
422                )),
423            }
424        } else {
425            Ok(())
426        }
427    }
428
429    /// Reject unusable token-read / prefix combinations.
430    /// 拒绝无法工作的读取开关 / 前缀组合。
431    pub fn validate_token_io(&self) -> SaTokenResult<()> {
432        if !self.is_read_header && !self.is_read_cookie && !self.is_read_body {
433            return Err(SaTokenError::ConfigError(
434                "at least one of is_read_header, is_read_cookie, is_read_body must be true".into(),
435            ));
436        }
437        if let Some(p) = self.token_prefix.as_deref() {
438            if p.is_empty() {
439                return Err(SaTokenError::ConfigError(
440                    "token_prefix cannot be empty; use None to disable a custom prefix".into(),
441                ));
442            }
443        }
444        Ok(())
445    }
446
447    /// 权限缓存 TTL 的 `Duration` 形式;返回 `None` 表示**不启用缓存**。
448    ///
449    /// The grant cache TTL as a `Duration`; `None` means the cache is disabled.
450    pub fn grant_cache_duration(&self) -> Option<Duration> {
451        if self.grant_cache_ttl > 0 {
452            Some(Duration::from_secs(self.grant_cache_ttl as u64))
453        } else {
454            None
455        }
456    }
457
458    /// 构造存储键:拼接 `storage_key_prefix` 与后缀。
459    /// Build a storage key by joining `storage_key_prefix` and a suffix.
460    ///
461    /// # Deprecated
462    ///
463    /// 请改用 [`SaKeys`] 具名方法,以尊重键布局策略。
464    /// Use [`SaKeys`] named methods instead so key layout is respected.
465    #[deprecated(
466        since = "0.2.0",
467        note = "Use SaKeys named key methods (token_info / login_token / ...) instead"
468    )]
469    pub fn make_key(&self, suffix: &str, id: &str) -> String {
470        format!("{}{}{}", self.storage_key_prefix, suffix, id)
471    }
472
473    /// 获取存储键前缀 | Get the storage key prefix
474    pub fn key_prefix(&self) -> &str {
475        &self.storage_key_prefix
476    }
477
478    /// 将领域对象编码为存储字符串 | Encode a domain object into a storage string
479    pub fn encode<T: Serialize + ?Sized>(&self, value: &T) -> SaTokenResult<String> {
480        self.serializer.encode(value).map_err(SaTokenError::from)
481    }
482
483    /// 从存储字符串解码领域对象 | Decode a domain object from a storage string
484    pub fn decode<T: DeserializeOwned>(&self, raw: &str) -> SaTokenResult<T> {
485        self.serializer.decode(raw).map_err(SaTokenError::from)
486    }
487}
488
489/// Token 风格 | Token style
490#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
491pub enum TokenStyle {
492    /// UUID 风格 | UUID style
493    Uuid,
494    /// 简化 UUID(去掉横杠)| Simple UUID (without hyphens)
495    SimpleUuid,
496    /// 32 位随机字符串 | 32-character random string
497    Random32,
498    /// 64 位随机字符串 | 64-character random string
499    Random64,
500    /// 128 位随机字符串 | 128-character random string
501    Random128,
502    /// JWT 风格(JSON Web Token)| JWT style (JSON Web Token)
503    Jwt,
504    /// Hash 风格(SHA256)| Hash style (SHA256)
505    Hash,
506    /// 时间戳风格(毫秒时间戳 + 随机数)| Timestamp style (ms timestamp + random)
507    Timestamp,
508    /// Tik 风格(短小的 8 位字符)| Tik style (short 8-character token)
509    Tik,
510}
511
512/// How a session is ended: normal logout, kick-out, or replaced.
513/// 会话结束方式:正常登出、踢下线、或顶号替换。
514#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
515pub enum LogoutMode {
516    /// 正常登出 | Normal logout
517    #[default]
518    Logout,
519    /// 踢下线(标记 `-5`)| Kick out (marker `-5`)
520    KickOut,
521    /// 顶下线(标记 `-4`)| Replaced / bumped offline (marker `-4`)
522    Replaced,
523}
524
525/// 非并发顶号时:踢旧设备还是拒绝新登录
526/// Non-concurrent replace policy: kick old device or reject new login
527#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
528pub enum ReplacedLoginExitMode {
529    /// 踢掉旧设备,允许新登录 | Kick the old device and allow the new login
530    #[default]
531    OldDevice,
532    /// 拒绝新登录,保留旧设备 | Reject the new login and keep the old device
533    NewDevice,
534}
535
536/// 顶号影响范围 | Scope of a replace (bump) operation
537#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
538pub enum ReplacedRange {
539    /// 仅当前设备类型 | Current device type only
540    #[default]
541    CurrDeviceType,
542    /// 全部设备类型 | All device types
543    AllDeviceType,
544}
545
546/// 注入只读 `StpInterface` 时,权限/角色**写操作**的处理策略。
547///
548/// Write policy for permission/role mutations when a **read-only**
549/// `StpInterface` is injected.
550#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
551pub enum GrantWritePolicy {
552    /// 静默写入 storage,不告警
553    /// Write to storage silently; use only when the caller knows the trade-off.
554    Allow,
555
556    /// 写入 storage 并输出 `tracing::warn!`(默认,向后兼容且不静默)
557    /// Write to storage and emit a `tracing::warn!`. Default: compatible, not silent.
558    #[default]
559    Warn,
560
561    /// 直接拒绝写操作,返回 `SaTokenError::ConfigError`
562    /// Reject the write outright with `SaTokenError::ConfigError`.
563    Reject,
564}
565
566/// Logout range for [`AuthService::logout`] / default config.
567/// [`AuthService::logout`] / 默认配置使用的登出范围。
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
569pub enum LogoutRange {
570    /// Current token only | 仅当前 token
571    #[default]
572    Token,
573    /// Entire account | 整个账号
574    Account,
575}
576
577/// 配置构建器 | Configuration builder
578#[derive(Default)]
579pub struct SaTokenConfigBuilder {
580    /// 累积中的配置 | Accumulated configuration
581    config: SaTokenConfig,
582    /// 可选存储适配器 | Optional storage adapter
583    storage: Option<Arc<dyn SaStorage>>,
584    /// 待注册的事件监听器 | Event listeners to register on build
585    listeners: Vec<Arc<dyn SaTokenListener>>,
586    /// 可选序列化器覆盖 | Optional serializer override
587    serializer: Option<SharedSerializer>,
588    /// 可选共享事件总线 | Optional shared event bus
589    event_bus: Option<SaTokenEventBus>,
590}
591
592impl std::fmt::Debug for SaTokenConfigBuilder {
593    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
594        f.write_str("SaTokenConfigBuilder { .. }")
595    }
596}
597
598impl SaTokenConfigBuilder {
599    /// 设置 Token 名称 | Set the token name
600    pub fn token_name(mut self, name: impl Into<String>) -> Self {
601        self.config.token_name = name.into();
602        self
603    }
604
605    /// 设置 Token 有效期(秒)| Set token lifetime in seconds
606    pub fn timeout(mut self, timeout: i64) -> Self {
607        self.config.timeout = timeout;
608        self
609    }
610
611    /// 设置最低活跃频率(秒)| Set the minimum activity interval in seconds
612    pub fn active_timeout(mut self, timeout: i64) -> Self {
613        self.config.active_timeout = timeout;
614        self
615    }
616
617    /// 设置是否启用 per-token 动态 `active_timeout`
618    /// Enable or disable per-token dynamic `active_timeout`
619    pub fn dynamic_active_timeout(mut self, enabled: bool) -> Self {
620        self.config.dynamic_active_timeout = enabled;
621        self
622    }
623
624    /// 设置是否开启自动续签 | Enable or disable auto-renewal
625    pub fn auto_renew(mut self, enabled: bool) -> Self {
626        self.config.auto_renew = enabled;
627        self
628    }
629
630    /// 设置续签阈值(秒)。传入负值等价于关闭阈值(每次读取都续签)。
631    /// Set the renewal threshold in seconds. A negative value disables the
632    /// threshold, restoring the legacy "renew on every read" behaviour.
633    pub fn renew_threshold(mut self, seconds: i64) -> Self {
634        self.config.renew_threshold = seconds;
635        self
636    }
637
638    /// 设置是否允许并发登录 | Enable or disable concurrent logins
639    pub fn is_concurrent(mut self, concurrent: bool) -> Self {
640        self.config.is_concurrent = concurrent;
641        self
642    }
643
644    /// 设置是否共享 token | Enable or disable shared tokens across concurrent logins
645    pub fn is_share(mut self, share: bool) -> Self {
646        self.config.is_share = share;
647        self
648    }
649
650    /// 设置 Token 风格 | Set the token generation style
651    pub fn token_style(mut self, style: TokenStyle) -> Self {
652        self.config.token_style = style;
653        self
654    }
655
656    /// Emit operation logs at info level when true.
657    /// 为 true 时在 info 级别输出操作日志。
658    pub fn is_log(mut self, enabled: bool) -> Self {
659        self.config.is_log = enabled;
660        self
661    }
662
663    /// Read token from headers (including Authorization fallback).
664    /// 是否从请求头读取 token(含 Authorization 回退)。
665    pub fn is_read_header(mut self, enabled: bool) -> Self {
666        self.config.is_read_header = enabled;
667        self
668    }
669
670    /// Read token from cookies.
671    /// 是否从 Cookie 读取 token。
672    pub fn is_read_cookie(mut self, enabled: bool) -> Self {
673        self.config.is_read_cookie = enabled;
674        self
675    }
676
677    /// Read token from query/param (`is_read_body` name kept for compatibility).
678    /// 是否从 query/param 读取 token(字段名 `is_read_body` 保持兼容)。
679    pub fn is_read_body(mut self, enabled: bool) -> Self {
680        self.config.is_read_body = enabled;
681        self
682    }
683
684    /// Custom token prefix; empty string is rejected at build time.
685    /// 自定义 token 前缀;空字符串在构建时拒绝。
686    pub fn token_prefix(mut self, prefix: impl Into<String>) -> Self {
687        self.config.token_prefix = Some(prefix.into());
688        self
689    }
690
691    /// Opt-in cookie write for handlers using `write_token_cookie` (default false).
692    /// Handler 调用 `write_token_cookie` 时是否真正写入 Cookie(默认 false)。
693    pub fn is_write_cookie(mut self, write: bool) -> Self {
694        self.config.cookie.is_write_cookie = write;
695        self
696    }
697
698    /// Cookie Domain attribute.
699    /// Cookie 的 Domain 属性。
700    pub fn cookie_domain(mut self, domain: impl Into<String>) -> Self {
701        self.config.cookie.domain = Some(domain.into());
702        self
703    }
704
705    /// Cookie Path attribute.
706    /// Cookie 的 Path 属性。
707    pub fn cookie_path(mut self, path: impl Into<String>) -> Self {
708        self.config.cookie.path = Some(path.into());
709        self
710    }
711
712    /// Cookie HttpOnly flag.
713    /// Cookie 的 HttpOnly 标志。
714    pub fn cookie_http_only(mut self, http_only: bool) -> Self {
715        self.config.cookie.http_only = http_only;
716        self
717    }
718
719    /// Cookie Secure flag.
720    /// Cookie 的 Secure 标志。
721    pub fn cookie_secure(mut self, secure: bool) -> Self {
722        self.config.cookie.secure = secure;
723        self
724    }
725
726    /// Cookie SameSite attribute.
727    /// Cookie 的 SameSite 属性。
728    pub fn cookie_same_site(mut self, same_site: sa_token_adapter::context::SameSite) -> Self {
729        self.config.cookie.same_site = Some(same_site);
730        self
731    }
732
733    /// 设置存储键前缀(默认 `"sa:"`)。
734    /// 此前缀用于 Redis / 数据库等存储后端的键命名。
735    ///
736    /// Set the storage key prefix (default `"sa:"`).
737    /// Used when naming keys in Redis/DB backends.
738    pub fn storage_key_prefix(mut self, prefix: impl Into<String>) -> Self {
739        self.config.storage_key_prefix = prefix.into();
740        self
741    }
742
743    /// 设置存储键布局(A3-1)| Set the storage key layout (A3-1)
744    pub fn key_layout(mut self, layout: SaKeyLayout) -> Self {
745        self.config.key_layout = layout;
746        self
747    }
748
749    /// 设置 JWT 密钥 | Set the JWT secret key
750    pub fn jwt_secret_key(mut self, key: impl Into<String>) -> Self {
751        self.config.jwt_secret_key = Some(key.into());
752        self
753    }
754
755    /// 设置 JWT 算法 | Set the JWT algorithm
756    pub fn jwt_algorithm(mut self, algorithm: impl Into<String>) -> Self {
757        self.config.jwt_algorithm = Some(algorithm.into());
758        self
759    }
760
761    /// 设置 JWT 签发者 | Set the JWT issuer
762    pub fn jwt_issuer(mut self, issuer: impl Into<String>) -> Self {
763        self.config.jwt_issuer = Some(issuer.into());
764        self
765    }
766
767    /// 设置 JWT 受众 | Set the JWT audience
768    pub fn jwt_audience(mut self, audience: impl Into<String>) -> Self {
769        self.config.jwt_audience = Some(audience.into());
770        self
771    }
772
773    /// 设置 JWT 失败时是否回退 UUID | Enable or disable UUID fallback on JWT failure
774    pub fn jwt_fallback_on_error(mut self, fallback: bool) -> Self {
775        self.config.jwt_fallback_on_error = fallback;
776        self
777    }
778
779    /// 启用防重放攻击(nonce 机制)| Enable anti-replay protection via nonce
780    pub fn enable_nonce(mut self, enable: bool) -> Self {
781        self.config.enable_nonce = enable;
782        self
783    }
784
785    /// 设置 Nonce 有效期(秒)| Set the nonce lifetime in seconds
786    pub fn nonce_timeout(mut self, timeout: i64) -> Self {
787        self.config.nonce_timeout = timeout;
788        self
789    }
790
791    /// 启用 Refresh Token | Enable refresh tokens
792    pub fn enable_refresh_token(mut self, enable: bool) -> Self {
793        self.config.enable_refresh_token = enable;
794        self
795    }
796
797    /// 设置 Refresh Token 有效期(秒)| Set the refresh-token lifetime in seconds
798    pub fn refresh_token_timeout(mut self, timeout: i64) -> Self {
799        self.config.refresh_token_timeout = timeout;
800        self
801    }
802
803    /// 设置同一账号最大登录数量 | Set max concurrent logins per account
804    pub fn max_login_count(mut self, count: i64) -> Self {
805        self.config.max_login_count = count;
806        self
807    }
808
809    /// 设置超出最大登录数时的下线模式 | Set the overflow logout mode
810    pub fn overflow_logout_mode(mut self, mode: LogoutMode) -> Self {
811        self.config.overflow_logout_mode = mode;
812        self
813    }
814
815    /// 设置非并发顶号退出策略 | Set the non-concurrent replace exit mode
816    pub fn replaced_login_exit_mode(mut self, mode: ReplacedLoginExitMode) -> Self {
817        self.config.replaced_login_exit_mode = mode;
818        self
819    }
820
821    /// 设置顶号范围 | Set the replace scope
822    pub fn replaced_range(mut self, range: ReplacedRange) -> Self {
823        self.config.replaced_range = range;
824        self
825    }
826
827    /// 设置登录时是否立即创建 Token-Session
828    /// Enable or disable creating a Token-Session immediately on login
829    pub fn right_now_create_token_session(mut self, enabled: bool) -> Self {
830        self.config.right_now_create_token_session = enabled;
831        self
832    }
833
834    /// 设置获取 Token-Session 时是否校验登录态
835    /// Enable or disable login check when fetching a Token-Session
836    pub fn token_session_check_login(mut self, enabled: bool) -> Self {
837        self.config.token_session_check_login = enabled;
838        self
839    }
840
841    /// 设置默认 logout 范围 | Set the default logout range
842    pub fn logout_range(mut self, range: LogoutRange) -> Self {
843        self.config.logout_range = range;
844        self
845    }
846
847    /// 设置 logout 时是否保留 Token-Session
848    /// Enable or disable keeping the Token-Session on logout
849    pub fn is_logout_keep_token_session(mut self, keep: bool) -> Self {
850        self.config.is_logout_keep_token_session = keep;
851        self
852    }
853
854    /// 设置权限/角色缓存 TTL(秒)。`<= 0` 关闭缓存。
855    /// Sets the permission/role cache TTL in seconds; `<= 0` disables it.
856    pub fn grant_cache_ttl(mut self, seconds: i64) -> Self {
857        self.config.grant_cache_ttl = seconds;
858        self
859    }
860
861    /// 设置缓存条目上限(跨全部分片的总量)。
862    /// Sets the total cache capacity across all shards.
863    pub fn grant_cache_max_entries(mut self, max: usize) -> Self {
864        self.config.grant_cache_max_entries = max;
865        self
866    }
867
868    /// 开关单飞(并发未命中合并为一次底层加载)。
869    /// Toggles single-flight loading for concurrent cache misses.
870    pub fn grant_cache_single_flight(mut self, enabled: bool) -> Self {
871        self.config.grant_cache_single_flight = enabled;
872        self
873    }
874
875    /// 开关请求级授权快照。
876    /// Toggles the per-request authorization snapshot.
877    pub fn grant_request_scope(mut self, enabled: bool) -> Self {
878        self.config.grant_request_scope = enabled;
879        self
880    }
881
882    /// 设置只读 `StpInterface` 下的写策略。
883    /// Sets the write policy used with a read-only `StpInterface`.
884    pub fn grant_write_policy(mut self, policy: GrantWritePolicy) -> Self {
885        self.config.grant_write_policy = policy;
886        self
887    }
888
889    /// Toggle role wildcard matching (default: exact).
890    /// 开关角色通配符匹配(默认精确匹配)。
891    pub fn role_wildcard(mut self, enabled: bool) -> Self {
892        self.config.role_wildcard = enabled;
893        self
894    }
895
896    /// 设置 `context_auto_create`
897    /// Set whether `with_current_mut` should auto-create an empty context.
898    pub fn context_auto_create(mut self, enable: bool) -> Self {
899        self.config.context_auto_create = enable;
900        self
901    }
902
903    /// HTTP Basic account (`user:password`)
904    /// HTTP Basic 账号(`user:password`)
905    pub fn http_basic(mut self, account: impl Into<String>) -> Self {
906        self.config.http_basic = account.into();
907        self
908    }
909
910    /// Same-Token TTL in seconds
911    /// Same-Token 有效期(秒)
912    pub fn same_token_timeout(mut self, timeout: i64) -> Self {
913        self.config.same_token_timeout = timeout;
914        self
915    }
916
917    /// Same-Token header name
918    /// Same-Token 请求头名
919    pub fn same_token_header(mut self, name: impl Into<String>) -> Self {
920        self.config.same_token_header = name.into();
921        self
922    }
923
924    /// Max attempts when allocating a unique login / temp token (`-1` = no retry).
925    /// 分配唯一登录/临时 token 的最大尝试次数(`-1` 表示不重试)。
926    pub fn max_try_times(mut self, n: i32) -> Self {
927        self.config.max_try_times = n;
928        self
929    }
930
931    /// HMAC secret for `RequestSign` via StpUtil (independent from JWT).
932    /// StpUtil 使用的 HMAC 密钥(与 JWT 密钥分离)。
933    pub fn sign_secret_key(mut self, key: impl Into<String>) -> Self {
934        self.config.sign_secret_key = Some(key.into());
935        self
936    }
937
938    /// Timestamp window in seconds for `RequestSign`.
939    /// `RequestSign` 的时间窗(秒)。
940    pub fn sign_window_secs(mut self, secs: i64) -> Self {
941        self.config.sign_window_secs = secs;
942        self
943    }
944
945    /// 设置存储层序列化器(默认 JSON)
946    /// Set the storage serializer (JSON by default)
947    pub fn serializer(mut self, serializer: SharedSerializer) -> Self {
948        self.serializer = Some(serializer);
949        self
950    }
951
952    /// 设置存储适配器 | Set the storage adapter
953    pub fn storage(mut self, storage: Arc<dyn SaStorage>) -> Self {
954        self.storage = Some(storage);
955        self
956    }
957
958    /// 注入共享事件总线;未设置时 Manager::new 内部创建默认 bus
959    ///
960    /// Injects a shared event bus; if not set, Manager::new creates a default bus internally.
961    pub fn event_bus(mut self, bus: SaTokenEventBus) -> Self {
962        self.event_bus = Some(bus);
963        self
964    }
965
966    /// 注册事件监听器(可多次调用以注册多个)。
967    /// Register an event listener (call multiple times for multiple listeners).
968    ///
969    /// # 示例 | Example
970    /// ```rust,ignore
971    /// use std::sync::Arc;
972    /// use sa_token_core::{SaTokenConfig, SaTokenListener};
973    ///
974    /// struct MyListener;
975    /// impl SaTokenListener for MyListener { /* ... */ }
976    ///
977    /// let manager = SaTokenConfig::builder()
978    ///     .storage(Arc::new(MemoryStorage::new()))
979    ///     .register_listener(Arc::new(MyListener))
980    ///     .build();
981    /// ```
982    pub fn register_listener(mut self, listener: Arc<dyn SaTokenListener>) -> Self {
983        self.listeners.push(listener);
984        self
985    }
986
987    /// 构建 `SaTokenManager`(需先设置 `storage`)。
988    ///
989    /// 自动完成:
990    /// 1. 创建 `SaTokenManager`
991    /// 2. 注册所有事件监听器
992    /// 3. 初始化 `StpUtil`
993    ///
994    /// Build a `SaTokenManager` (`storage` must be set first).
995    ///
996    /// Automatically:
997    /// 1. Creates `SaTokenManager`
998    /// 2. Registers all event listeners
999    /// 3. Initializes `StpUtil`
1000    ///
1001    /// # Panics
1002    /// 未设置 `storage` 时 panic。
1003    /// Panics if `storage` was not set.
1004    ///
1005    /// # 示例 | Example
1006    /// ```rust,ignore
1007    /// use std::sync::Arc;
1008    /// use sa_token_core::SaTokenConfig;
1009    /// use sa_token_storage_memory::MemoryStorage;
1010    ///
1011    /// // 一行完成初始化 | Complete initialization in one line
1012    /// SaTokenConfig::builder()
1013    ///     .storage(Arc::new(MemoryStorage::new()))
1014    ///     .timeout(7200)
1015    ///     .register_listener(Arc::new(MyListener))
1016    ///     .build();
1017    /// ```
1018    #[allow(clippy::panic)]
1019    pub fn build(self) -> crate::SaTokenManager {
1020        self.try_build()
1021            .unwrap_or_else(|e| panic!("SaTokenConfigBuilder::build failed: {e}"))
1022    }
1023
1024    /// Build Manager; JWT misconfig returns Err instead of panicking later.
1025    /// 构造 Manager;JWT 配错在此处返回 Err,而不是延后 panic。
1026    pub fn try_build(self) -> SaTokenResult<crate::SaTokenManager> {
1027        let manager = self.try_build_manager_only()?;
1028        if let Err(SaTokenError::AlreadyInitialized) =
1029            crate::StpUtil::try_init_manager(manager.clone())
1030        {
1031            tracing::warn!(
1032                "StpUtil already initialized; returning Manager without replacing global instance"
1033            );
1034        }
1035        Ok(manager)
1036    }
1037
1038    /// 仅构造 Manager:注册监听器 / 注入 EventBus,**不**写入全局 StpUtil。
1039    /// Construct Manager only: listeners + event bus; do **not** touch global StpUtil.
1040    #[allow(clippy::panic)]
1041    pub fn build_manager_only(self) -> crate::SaTokenManager {
1042        self.try_build_manager_only()
1043            .unwrap_or_else(|e| panic!("SaTokenConfigBuilder::build_manager_only failed: {e}"))
1044    }
1045
1046    fn try_build_manager_only(self) -> SaTokenResult<crate::SaTokenManager> {
1047        let mut config = self.config;
1048        if let Some(serializer) = self.serializer {
1049            config.serializer = serializer;
1050        }
1051        config.validate_jwt()?;
1052        config.validate_token_io()?;
1053        let storage = self.storage.ok_or_else(|| {
1054            SaTokenError::ConfigError("Storage must be set before building SaTokenManager".into())
1055        })?;
1056        let mut manager = crate::SaTokenManager::new(storage, config);
1057
1058        if let Some(bus) = self.event_bus {
1059            manager = manager.with_event_bus(bus);
1060        }
1061
1062        if !self.listeners.is_empty() {
1063            let event_bus = manager.event_bus();
1064            for listener in self.listeners {
1065                event_bus.register(listener);
1066            }
1067        }
1068
1069        Ok(manager)
1070    }
1071
1072    /// 仅构建配置(不创建 Manager)
1073    /// Build the config only (without creating a Manager)
1074    #[allow(clippy::panic)]
1075    pub fn build_config(self) -> SaTokenConfig {
1076        self.try_build_config()
1077            .unwrap_or_else(|e| panic!("SaTokenConfigBuilder::build_config failed: {e}"))
1078    }
1079
1080    /// Build config after JWT validation (does not construct Manager).
1081    /// 校验 JWT 后只构建配置(不构造 Manager)。
1082    pub fn try_build_config(self) -> SaTokenResult<SaTokenConfig> {
1083        let mut config = self.config;
1084        if let Some(serializer) = self.serializer {
1085            config.serializer = serializer;
1086        }
1087        config.validate_jwt()?;
1088        Ok(config)
1089    }
1090}