Skip to main content

sa_token_core/token/
mod.rs

1// Author: 金书记
2//
3//! Token 管理模块
4
5use std::sync::{Arc, OnceLock};
6
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10mod csprng;
11pub mod generator;
12pub mod jwt;
13pub mod map;
14pub mod validator;
15
16pub(crate) use csprng::random_hex;
17pub use generator::{TokenGenerator, generate_unique};
18pub use jwt::{JwtAlgorithm, JwtClaims, JwtManager};
19pub use validator::TokenValidator;
20
21/// Token 字节在请求内会被 TokenInfo / 上下文多次 Clone;用 Arc 避免复制。
22/// Token bytes are cloned across TokenInfo and request context; Arc avoids copying.
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24pub struct TokenValue(Arc<str>);
25
26impl TokenValue {
27    /// Create a new instance | 创建新实例
28    pub fn new(value: impl AsRef<str>) -> Self {
29        Self(Arc::from(value.as_ref()))
30    }
31
32    /// `as_str` — as str | `as_str`
33    #[inline]
34    pub fn as_str(&self) -> &str {
35        &self.0
36    }
37}
38
39impl Serialize for TokenValue {
40    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
41        serializer.serialize_str(&self.0)
42    }
43}
44
45impl<'de> Deserialize<'de> for TokenValue {
46    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
47        let s = String::deserialize(deserializer)?;
48        Ok(Self(Arc::from(s)))
49    }
50}
51
52impl From<String> for TokenValue {
53    fn from(s: String) -> Self {
54        Self(Arc::from(s))
55    }
56}
57
58impl From<&str> for TokenValue {
59    fn from(s: &str) -> Self {
60        Self(Arc::from(s))
61    }
62}
63
64impl From<TokenValue> for String {
65    fn from(v: TokenValue) -> Self {
66        v.0.to_string()
67    }
68}
69
70impl std::fmt::Display for TokenValue {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        f.write_str(&self.0)
73    }
74}
75
76/// 默认账号体系字符串只 intern 一次。这是 OnceLock 的正确用途(常量),不是替代 Arc。
77/// Intern the default login-type string once. A legitimate OnceLock use (a constant), not an Arc replacement.
78pub fn intern_login_type(s: &str) -> Arc<str> {
79    if s.is_empty() || s == crate::keys::LOGIN_TYPE_DEFAULT || s == "login" {
80        static DEFAULT: OnceLock<Arc<str>> = OnceLock::new();
81        return DEFAULT
82            .get_or_init(|| Arc::from(crate::keys::LOGIN_TYPE_DEFAULT))
83            .clone();
84    }
85    Arc::from(s)
86}
87
88/// Serde helpers for `Arc<str>` (serde has no built-in Arc<str> without `rc` + owned form).
89/// `Arc<str>` 的 serde 辅助(无内置支持时走 String 往返)。
90mod arc_str_serde {
91    use serde::{Deserialize, Deserializer, Serializer};
92    use std::sync::Arc;
93
94    pub(super) fn serialize<S: Serializer>(
95        value: &Arc<str>,
96        serializer: S,
97    ) -> Result<S::Ok, S::Error> {
98        serializer.serialize_str(value)
99    }
100
101    pub(super) fn deserialize<'de, D: Deserializer<'de>>(
102        deserializer: D,
103    ) -> Result<Arc<str>, D::Error> {
104        let s = String::deserialize(deserializer)?;
105        Ok(Arc::from(s))
106    }
107}
108
109/// Token 信息 | Token Information
110///
111/// 存储 Token 的完整信息,包括元数据和安全特性
112/// Stores complete token information, including metadata and security features
113///
114/// # 字段说明 | Field Description
115/// - `token`: Token 值 | Token value
116/// - `login_id`: 登录用户 ID | Logged-in user ID
117/// - `login_type`: 登录类型(如 "user", "admin")| Login type (e.g., "user", "admin")
118/// - `create_time`: Token 创建时间 | Token creation time
119/// - `last_active_time`: 最后活跃时间 | Last active time
120/// - `expire_time`: 过期时间(None 表示永不过期)| Expiration time (None means never expires)
121/// - `device`: 设备标识 | Device identifier
122/// - `extra_data`: 额外数据 | Extra data
123/// - `nonce`: 防重放攻击的一次性令牌 | One-time token for replay attack prevention
124/// - `refresh_token`: 用于刷新的长期令牌 | Long-term token for refresh
125/// - `refresh_token_expire_time`: Refresh Token 过期时间 | Refresh token expiration time
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct TokenInfo {
128    /// Token 值 | Token value
129    pub token: TokenValue,
130
131    /// 登录 ID(Arc 共享,避免请求内多次 Clone 拷贝字节)
132    /// Login ID (Arc-shared to avoid copying bytes on request-path clones)
133    #[serde(with = "arc_str_serde")]
134    pub login_id: Arc<str>,
135
136    /// 登录类型(默认体系经 [`intern_login_type`] 复用同一 Arc)
137    /// Login type (default systems reuse one Arc via [`intern_login_type`])
138    #[serde(with = "arc_str_serde")]
139    pub login_type: Arc<str>,
140
141    /// Token 创建时间 | Token creation time
142    pub create_time: DateTime<Utc>,
143
144    /// Token 最后活跃时间 | Token last active time
145    pub last_active_time: DateTime<Utc>,
146
147    /// Token 过期时间(None 表示永不过期)| Token expiration time (None means never expires)
148    pub expire_time: Option<DateTime<Utc>>,
149
150    /// 设备标识 | Device identifier
151    pub device: Option<String>,
152
153    /// 额外数据 | Extra data
154    pub extra_data: Option<serde_json::Value>,
155
156    /// Nonce(用于防重放攻击)| Nonce (for replay attack prevention)
157    pub nonce: Option<String>,
158
159    /// Refresh Token(用于刷新访问令牌)| Refresh Token (for refreshing access token)
160    pub refresh_token: Option<String>,
161
162    /// Refresh Token 过期时间 | Refresh Token expiration time
163    pub refresh_token_expire_time: Option<DateTime<Utc>>,
164
165    /// Per-token idle timeout (seconds). Used only when `dynamic_active_timeout` is on.
166    /// 单 token 闲置超时(秒)。仅 `dynamic_active_timeout` 打开时使用。
167    #[serde(default)]
168    pub active_timeout_override: Option<i64>,
169}
170
171impl TokenInfo {
172    /// Create a new instance | 创建新实例
173    pub fn new(token: TokenValue, login_id: impl AsRef<str>) -> Self {
174        let now = Utc::now();
175        Self {
176            token,
177            login_id: Arc::from(login_id.as_ref()),
178            login_type: intern_login_type(crate::keys::LOGIN_TYPE_DEFAULT),
179            create_time: now,
180            last_active_time: now,
181            expire_time: None,
182            device: None,
183            extra_data: None,
184            nonce: None,
185            refresh_token: None,
186            refresh_token_expire_time: None,
187            active_timeout_override: None,
188        }
189    }
190
191    /// Idle limit actually used for freeze checks.
192    /// 冻结检查实际使用的闲置上限。
193    pub fn effective_active_timeout(&self, config: &crate::config::SaTokenConfig) -> i64 {
194        if config.dynamic_active_timeout {
195            self.active_timeout_override
196                .unwrap_or(config.active_timeout)
197        } else {
198            config.active_timeout
199        }
200    }
201
202    /// `is_expired` — is expired | `is_expired`
203    pub fn is_expired(&self) -> bool {
204        if let Some(expire_time) = self.expire_time {
205            Utc::now() > expire_time
206        } else {
207            false
208        }
209    }
210
211    /// `update_active_time` — update active time | `update_active_time`
212    pub fn update_active_time(&mut self) {
213        self.last_active_time = Utc::now();
214    }
215
216    /// True when idle longer than `active_timeout`.
217    /// 空闲超过 `active_timeout` 时为 true。
218    ///
219    /// `active_timeout <= 0` means never freeze; otherwise compares now vs `last_active_time`.
220    pub fn is_freeze(&self, active_timeout: i64) -> bool {
221        if active_timeout <= 0 {
222            return false;
223        }
224        Utc::now()
225            .signed_duration_since(self.last_active_time)
226            .num_seconds()
227            > active_timeout
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn test_is_freeze_respects_active_timeout() {
237        let mut info = TokenInfo::new(TokenValue::new("t"), "u");
238        info.last_active_time = Utc::now() - chrono::Duration::seconds(120);
239        assert!(info.is_freeze(60));
240        assert!(!info.is_freeze(-1));
241        assert!(!info.is_freeze(0));
242    }
243}
244
245/// Token 签名
246#[derive(Debug, Clone)]
247pub struct TokenSign {
248    /// `value` | `value`
249    pub value: String,
250    /// Device / terminal label | 设备/终端标识
251    pub device: Option<String>,
252}