sa_token_core/
nonce.rs

1// Author: 金书记
2//
3//! Nonce Manager | Nonce 管理器
4//!
5//! Prevents replay attacks by tracking used nonces
6//! 通过跟踪已使用的 nonce 来防止重放攻击
7//!
8//! ## Overview | 概述
9//!
10//! A **nonce** (number used once) is a unique value that can only be used one time,
11//! preventing replay attacks where an attacker reuses a valid request.
12//! **nonce**(一次性数字)是一个只能使用一次的唯一值,防止攻击者重用有效请求的重放攻击。
13//!
14//! ## Integration with Sa-Token | 与 Sa-Token 的集成
15//!
16//! Nonce is used in several Sa-Token scenarios:
17//! Nonce 在 Sa-Token 的多个场景中使用:
18//!
19//! 1. **Login with Nonce** | 带 Nonce 的登录
20//!    - Prevents replay of login requests
21//!    - 防止登录请求的重放
22//!
23//! 2. **Token Creation** | Token 创建
24//!    - Each token can have an associated nonce
25//!    - 每个 token 可以关联一个 nonce
26//!
27//! 3. **OAuth2 / SSO** | OAuth2 / SSO
28//!    - Used in authorization codes and state parameters
29//!    - 用于授权码和状态参数
30//!
31//! 4. **Sensitive Operations** | 敏感操作
32//!    - Password changes, account deletion, etc.
33//!    - 密码修改、账户删除等
34//!
35//! ## Workflow | 工作流程
36//!
37//! ```text
38//! ┌─────────────────────────────────────────────────────────────┐
39//! │                    Nonce Lifecycle                          │
40//! │                    Nonce 生命周期                            │
41//! └─────────────────────────────────────────────────────────────┘
42//!
43//! Client                     NonceManager              Storage
44//! 客户端                     Nonce管理器               存储
45//!   │                             │                        │
46//!   │  1. Request nonce           │                        │
47//!   │  请求 nonce                 │                        │
48//!   │────────────────────────────▶│                        │
49//!   │                             │                        │
50//!   │  2. generate()              │                        │
51//!   │                             │  生成唯一 nonce        │
52//!   │                             │  nonce_TIMESTAMP_UUID  │
53//!   │                             │                        │
54//!   │  3. Return nonce            │                        │
55//!   │  返回 nonce                 │                        │
56//!   │◀────────────────────────────│                        │
57//!   │                             │                        │
58//!   │  4. Use nonce in request    │                        │
59//!   │  在请求中使用 nonce         │                        │
60//!   │────────────────────────────▶│                        │
61//!   │                             │                        │
62//!   │  5. validate_and_consume()  │                        │
63//!   │                             │  Check not used        │
64//!   │                             │  检查未使用             │
65//!   │                             │─────────────────────▶  │
66//!   │                             │  Get nonce key         │
67//!   │                             │                        │
68//!   │                             │  Not found = valid     │
69//!   │                             │  未找到 = 有效          │
70//!   │                             │◀─────────────────────  │
71//!   │                             │                        │
72//!   │                             │  Store nonce (TTL)     │
73//!   │                             │  存储 nonce            │
74//!   │                             │─────────────────────▶  │
75//!   │                             │                        │
76//!   │  6. Request processed       │                        │
77//!   │  请求已处理                 │                        │
78//!   │◀────────────────────────────│                        │
79//!   │                             │                        │
80//!   │  7. Reuse same nonce (ATTACK)                        │
81//!   │  重用相同 nonce(攻击)     │                        │
82//!   │────────────────────────────▶│                        │
83//!   │                             │  Check if used         │
84//!   │                             │  检查是否已使用         │
85//!   │                             │─────────────────────▶  │
86//!   │                             │  Found = already used  │
87//!   │                             │  找到 = 已使用          │
88//!   │                             │◀─────────────────────  │
89//!   │                             │                        │
90//!   │  ❌ Reject (NonceAlreadyUsed)                        │
91//!   │  拒绝(Nonce已使用)         │                        │
92//!   │◀────────────────────────────│                        │
93//!   │                             │                        │
94//!   │                          [After TTL expires]         │
95//!   │                          [TTL 过期后]                 │
96//!   │                             │   Auto cleanup         │
97//!   │                             │   自动清理              │
98//!   │                             │         X──────────────│
99//! ```
100//!
101//! ## Storage Keys | 存储键格式
102//!
103//! ```text
104//! sa:nonce:{nonce_value}
105//!   - Stores: { "login_id": "...", "created_at": "..." }
106//!   - TTL: Configured timeout (default: 60 seconds)
107//!   - Purpose: Mark nonce as used
108//!   
109//!   存储:{ "login_id": "...", "created_at": "..." }
110//!   TTL:配置的超时时间(默认:60秒)
111//!   目的:标记 nonce 为已使用
112//! ```
113//!
114//! ## Security Considerations | 安全考虑
115//!
116//! ```text
117//! 1. ✅ One-Time Use | 一次性使用
118//!    - Nonce can only be used once
119//!    - Stored after first use to prevent reuse
120//!    
121//! 2. ✅ Time-Limited | 时间限制
122//!    - Nonces expire after timeout (default: 60s)
123//!    - Prevents storage bloat
124//!    
125//! 3. ✅ Unique Generation | 唯一生成
126//!    - UUID + timestamp ensures uniqueness
127//!    - Collision probability: negligible
128//!    
129//! 4. ✅ Timestamp Validation | 时间戳验证
130//!    - check_timestamp() validates time window
131//!    - Prevents time-based attacks
132//!    
133//! 5. ✅ Atomic Operations | 原子操作
134//!    - validate_and_consume() is atomic
135//!    - Prevents race conditions
136//! ```
137//!
138//! ## Usage Examples | 使用示例
139//!
140//! ### Example 1: Login with Nonce | 带 Nonce 的登录
141//!
142//! ```rust,ignore
143//! use sa_token_core::manager::SaTokenManager;
144//!
145//! // Client requests nonce
146//! let nonce = nonce_manager.generate();
147//! // Returns: "nonce_1234567890123_abc123def456"
148//!
149//! // Client sends login request with nonce
150//! let token = manager.login_with_options(
151//!     "user_123",
152//!     None,
153//!     None,
154//!     None,
155//!     Some(nonce.clone()),  // ← Nonce here
156//!     None,
157//! ).await?;
158//!
159//! // Server validates and consumes nonce (inside login_with_token_info)
160//! nonce_manager.validate_and_consume(&nonce, "user_123").await?;
161//! // ✅ First use: OK
162//! // ❌ Second use: NonceAlreadyUsed error
163//! ```
164//!
165//! ### Example 2: Sensitive Operation with Nonce | 带 Nonce 的敏感操作
166//!
167//! ```rust,ignore
168//! // Change password with nonce protection
169//! async fn change_password(
170//!     user_id: &str,
171//!     new_password: &str,
172//!     nonce: &str,
173//! ) -> Result<()> {
174//!     // Validate nonce
175//!     nonce_manager.validate_and_consume(nonce, user_id).await?;
176//!     
177//!     // Proceed with password change
178//!     update_password(user_id, new_password).await?;
179//!     
180//!     Ok(())
181//! }
182//! ```
183//!
184//! ## Best Practices | 最佳实践
185//!
186//! 1. **Always generate nonces server-side** | 始终在服务端生成 nonce
187//!    - Don't let clients generate their own nonces
188//!    - 不要让客户端生成自己的 nonce
189//!
190//! 2. **Use appropriate timeout** | 使用适当的超时时间
191//!    - Short timeout (30-60s) for most operations
192//!    - Longer timeout (5-10min) for complex flows
193//!    - 大多数操作使用短超时(30-60秒)
194//!    - 复杂流程使用较长超时(5-10分钟)
195//!
196//! 3. **Validate timestamp** | 验证时间戳
197//!    - Use check_timestamp() for additional validation
198//!    - 使用 check_timestamp() 进行额外验证
199//!
200//! 4. **One nonce per operation** | 每个操作一个 nonce
201//!    - Don't reuse nonces across different operations
202//!    - 不要在不同操作间重用 nonce
203//!
204//! 5. **Combine with other security measures** | 与其他安全措施结合
205//!    - Use nonces WITH authentication, not instead of it
206//!    - 将 nonce 与认证结合使用,而不是替代认证
207//! ```
208
209use std::sync::Arc;
210use chrono::{DateTime, Utc};
211use sa_token_adapter::storage::SaStorage;
212use crate::error::{SaTokenError, SaTokenResult};
213use uuid::Uuid;
214
215/// Nonce Manager | Nonce 管理器
216///
217/// Manages nonce generation and validation to prevent replay attacks
218/// 管理 nonce 的生成和验证以防止重放攻击
219#[derive(Clone)]
220pub struct NonceManager {
221    storage: Arc<dyn SaStorage>,
222    timeout: i64,
223}
224
225impl NonceManager {
226    /// Create new nonce manager | 创建新的 nonce 管理器
227    ///
228    /// # Arguments | 参数
229    ///
230    /// * `storage` - Storage backend | 存储后端
231    /// * `timeout` - Nonce validity period in seconds | Nonce 有效期(秒)
232    pub fn new(storage: Arc<dyn SaStorage>, timeout: i64) -> Self {
233        Self { storage, timeout }
234    }
235
236    /// Generate a new nonce | 生成新的 nonce
237    ///
238    /// Generates a unique nonce using timestamp + UUID to ensure uniqueness.
239    /// 使用时间戳 + UUID 生成唯一的 nonce 以确保唯一性。
240    ///
241    /// # Returns | 返回
242    ///
243    /// Unique nonce string in format: `nonce_{timestamp_ms}_{uuid}`
244    /// 格式为 `nonce_{时间戳_毫秒}_{uuid}` 的唯一 nonce 字符串
245    ///
246    /// # Format | 格式
247    ///
248    /// ```text
249    /// nonce_1234567890123_abc123def456
250    ///   │         │            │
251    ///   │         │            └─ UUID (32 hex chars)
252    ///   │         └─ Timestamp in milliseconds
253    ///   └─ Prefix
254    /// ```
255    ///
256    /// # Example | 示例
257    ///
258    /// ```ignore
259    /// let nonce = nonce_manager.generate();
260    /// // Returns: "nonce_1701234567890_a1b2c3d4e5f6..."
261    /// ```
262    pub fn generate(&self) -> String {
263        format!("nonce_{}_{}", Utc::now().timestamp_millis(), Uuid::new_v4().simple())
264    }
265
266    /// Store and mark nonce as used | 存储并标记 nonce 为已使用
267    ///
268    /// Stores the nonce in storage with TTL, marking it as "consumed".
269    /// 将 nonce 以 TTL 存储在存储中,标记为"已消费"。
270    ///
271    /// # Arguments | 参数
272    ///
273    /// * `nonce` - Nonce to store | 要存储的 nonce
274    /// * `login_id` - Associated user ID | 关联的用户ID
275    ///
276    /// # Storage Key | 存储键
277    ///
278    /// `sa:nonce:{nonce}` → `{"login_id": "...", "created_at": "..."}`
279    ///
280    /// # TTL Behavior | TTL 行为
281    ///
282    /// The nonce is automatically removed after the timeout period.
283    /// Nonce 会在超时期后自动移除。
284    ///
285    /// # Example | 示例
286    ///
287    /// ```ignore
288    /// nonce_manager.store("nonce_123_abc", "user_001").await?;
289    /// // Storage now contains: sa:nonce:nonce_123_abc (expires after timeout)
290    /// ```
291    pub async fn store(&self, nonce: &str, login_id: &str) -> SaTokenResult<()> {
292        let key = format!("sa:nonce:{}", nonce);
293        let value = serde_json::json!({
294            "login_id": login_id,
295            "created_at": Utc::now().to_rfc3339(),
296        }).to_string();
297
298        // Set TTL to automatically expire the nonce
299        let ttl = Some(std::time::Duration::from_secs(self.timeout as u64));
300        self.storage.set(&key, &value, ttl)
301            .await
302            .map_err(|e| SaTokenError::StorageError(e.to_string()))?;
303
304        Ok(())
305    }
306
307    /// Validate nonce and ensure it hasn't been used | 验证 nonce 并确保未被使用
308    ///
309    /// Checks if the nonce exists in storage. If it exists, it has been used.
310    /// 检查 nonce 是否存在于存储中。如果存在,则已被使用。
311    ///
312    /// # Arguments | 参数
313    ///
314    /// * `nonce` - Nonce to validate | 要验证的 nonce
315    ///
316    /// # Returns | 返回
317    ///
318    /// * `Ok(true)` - Valid (not used yet) | 有效(尚未使用)
319    /// * `Ok(false)` - Invalid (already used) | 无效(已使用)
320    ///
321    /// # Logic | 逻辑
322    ///
323    /// ```text
324    /// Nonce NOT in storage → Valid (can be used)
325    /// Nonce IN storage     → Invalid (already used)
326    /// 
327    /// Nonce 不在存储中 → 有效(可以使用)
328    /// Nonce 在存储中   → 无效(已使用)
329    /// ```
330    ///
331    /// # Example | 示例
332    ///
333    /// ```ignore
334    /// let is_valid = nonce_manager.validate("nonce_123").await?;
335    /// if is_valid {
336    ///     // Proceed with operation
337    /// } else {
338    ///     // Reject: nonce already used
339    /// }
340    /// ```
341    pub async fn validate(&self, nonce: &str) -> SaTokenResult<bool> {
342        let key = format!("sa:nonce:{}", nonce);
343        
344        // Check if nonce exists in storage
345        // 检查 nonce 是否存在于存储中
346        let exists = self.storage.get(&key)
347            .await
348            .map_err(|e| SaTokenError::StorageError(e.to_string()))?
349            .is_some();
350
351        // Valid if NOT exists (not used yet)
352        // 不存在则有效(尚未使用)
353        Ok(!exists)
354    }
355
356    /// Validate and consume nonce in one operation | 一次操作验证并消费 nonce
357    ///
358    /// This is the **primary method** for using nonces in Sa-Token.
359    /// It checks if the nonce is valid (not used) and immediately marks it as used.
360    /// 这是在 Sa-Token 中使用 nonce 的**主要方法**。
361    /// 它检查 nonce 是否有效(未使用)并立即将其标记为已使用。
362    ///
363    /// # Arguments | 参数
364    ///
365    /// * `nonce` - Nonce to validate and consume | 要验证和消费的 nonce
366    /// * `login_id` - Associated user ID | 关联的用户ID
367    ///
368    /// # Returns | 返回
369    ///
370    /// * `Ok(())` - Nonce is valid and now consumed | Nonce 有效且已消费
371    /// * `Err(NonceAlreadyUsed)` - Nonce has already been used | Nonce 已被使用
372    /// * `Err(StorageError)` - Storage operation failed | 存储操作失败
373    ///
374    /// # Security | 安全性
375    ///
376    /// This operation is **atomic** from the application perspective:
377    /// 此操作从应用程序角度来看是**原子性**的:
378    ///
379    /// 1. Check if nonce exists (validate)
380    /// 2. If valid, store it immediately (consume)
381    /// 3. Return success
382    ///
383    /// If two requests use the same nonce simultaneously, only one will succeed.
384    /// 如果两个请求同时使用相同的 nonce,只有一个会成功。
385    ///
386    /// # Integration with Login | 与登录集成
387    ///
388    /// ```ignore
389    /// // Inside SaTokenManager::login_with_token_info()
390    /// if let Some(nonce) = &token_info.nonce {
391    ///     self.nonce_manager
392    ///         .validate_and_consume(nonce, &login_id)
393    ///         .await?; // ← Prevents replay attacks
394    /// }
395    /// ```
396    ///
397    /// # Example | 示例
398    ///
399    /// ```ignore
400    /// // ✅ First use: Success
401    /// nonce_manager.validate_and_consume("nonce_123", "user_001").await?;
402    /// println!("Login successful");
403    ///
404    /// // ❌ Second use: Error
405    /// let result = nonce_manager.validate_and_consume("nonce_123", "user_001").await;
406    /// assert!(matches!(result, Err(SaTokenError::NonceAlreadyUsed)));
407    /// ```
408    pub async fn validate_and_consume(&self, nonce: &str, login_id: &str) -> SaTokenResult<()> {
409        // 1. Validate: check if nonce has NOT been used
410        // 验证:检查 nonce 是否未被使用
411        if !self.validate(nonce).await? {
412            return Err(SaTokenError::NonceAlreadyUsed);
413        }
414
415        // 2. Consume: store nonce to mark as used
416        // 消费:存储 nonce 以标记为已使用
417        self.store(nonce, login_id).await?;
418        
419        Ok(())
420    }
421
422    /// Extract timestamp from nonce and check if it's within valid time window
423    /// 从 nonce 中提取时间戳并检查是否在有效时间窗口内
424    ///
425    /// Provides **additional security** by validating the nonce timestamp.
426    /// This prevents time-based attacks and ensures nonces are fresh.
427    /// 通过验证 nonce 时间戳提供**额外的安全性**。
428    /// 这可以防止基于时间的攻击并确保 nonce 是新鲜的。
429    ///
430    /// # Arguments | 参数
431    ///
432    /// * `nonce` - Nonce to check | 要检查的 nonce
433    /// * `window_seconds` - Maximum age of nonce in seconds | Nonce 的最大年龄(秒)
434    ///
435    /// # Returns | 返回
436    ///
437    /// * `Ok(true)` - Timestamp is within the time window | 时间戳在时间窗口内
438    /// * `Ok(false)` - Timestamp is outside the time window (too old or future) | 时间戳在窗口外(太旧或未来)
439    /// * `Err(InvalidNonceFormat)` - Nonce format is invalid | Nonce 格式无效
440    /// * `Err(InvalidNonceTimestamp)` - Timestamp cannot be parsed | 时间戳无法解析
441    ///
442    /// # Use Case | 使用场景
443    ///
444    /// ```ignore
445    /// // Validate nonce and its timestamp
446    /// let nonce = request.get_nonce();
447    ///
448    /// // Check timestamp: max 60 seconds old
449    /// if !nonce_manager.check_timestamp(&nonce, 60)? {
450    ///     return Err("Nonce too old");
451    /// }
452    ///
453    /// // Then validate and consume
454    /// nonce_manager.validate_and_consume(&nonce, user_id).await?;
455    /// ```
456    ///
457    /// # Nonce Format | Nonce 格式
458    ///
459    /// Expected format: `nonce_{timestamp_ms}_{uuid}`
460    /// 期望格式:`nonce_{时间戳_毫秒}_{uuid}`
461    ///
462    /// # Security Note | 安全说明
463    ///
464    /// This check should be used **in addition to** `validate_and_consume()`,
465    /// not as a replacement. It provides defense-in-depth.
466    /// 此检查应与 `validate_and_consume()` **一起使用**,而不是替代。
467    /// 它提供了深度防御。
468    pub fn check_timestamp(&self, nonce: &str, window_seconds: i64) -> SaTokenResult<bool> {
469        // Parse nonce format: nonce_TIMESTAMP_UUID
470        // 解析 nonce 格式:nonce_时间戳_UUID
471        let parts: Vec<&str> = nonce.split('_').collect();
472        if parts.len() < 3 || parts[0] != "nonce" {
473            return Err(SaTokenError::InvalidNonceFormat);
474        }
475
476        // Extract and parse timestamp
477        // 提取并解析时间戳
478        let timestamp_ms = parts[1].parse::<i64>()
479            .map_err(|_| SaTokenError::InvalidNonceTimestamp)?;
480
481        let nonce_time = DateTime::from_timestamp_millis(timestamp_ms)
482            .ok_or_else(|| SaTokenError::InvalidNonceTimestamp)?;
483
484        // Calculate time difference
485        // 计算时间差
486        let now = Utc::now();
487        let diff = (now - nonce_time).num_seconds().abs();
488
489        // Check if within time window
490        // 检查是否在时间窗口内
491        Ok(diff <= window_seconds)
492    }
493
494    /// Clean up expired nonces (implementation depends on storage)
495    /// 清理过期的 nonce(实现依赖于存储)
496    ///
497    /// # Note | 注意
498    ///
499    /// Most storage backends (Redis, Memcached) automatically expire keys with TTL.
500    /// This method is provided for storage backends that don't support TTL.
501    /// 大多数存储后端(Redis、Memcached)会自动过期带 TTL 的键。
502    /// 此方法为不支持 TTL 的存储后端提供。
503    ///
504    /// # Automatic Cleanup | 自动清理
505    ///
506    /// - **Redis**: Uses EXPIRE command, automatic cleanup
507    /// - **Memory**: Built-in TTL support, automatic cleanup
508    /// - **Database**: May need manual cleanup (implement here)
509    ///
510    /// # Manual Implementation | 手动实现
511    ///
512    /// For databases without TTL support:
513    /// 对于不支持 TTL 的数据库:
514    ///
515    /// ```ignore
516    /// pub async fn cleanup_expired(&self) -> SaTokenResult<()> {
517    ///     let cutoff = Utc::now() - Duration::seconds(self.timeout);
518    ///     // DELETE FROM nonces WHERE created_at < cutoff
519    ///     Ok(())
520    /// }
521    /// ```
522    pub async fn cleanup_expired(&self) -> SaTokenResult<()> {
523        // Storage with TTL support will auto-cleanup
524        // 支持 TTL 的存储会自动清理
525        // 
526        // This is a no-op for Redis/Memory storage
527        // 对于 Redis/Memory 存储,这是一个空操作
528        Ok(())
529    }
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535    use sa_token_storage_memory::MemoryStorage;
536
537    #[tokio::test]
538    async fn test_nonce_generation() {
539        let storage = Arc::new(MemoryStorage::new());
540        let nonce_mgr = NonceManager::new(storage, 60);
541
542        let nonce1 = nonce_mgr.generate();
543        let nonce2 = nonce_mgr.generate();
544
545        assert_ne!(nonce1, nonce2);
546        assert!(nonce1.starts_with("nonce_"));
547    }
548
549    #[tokio::test]
550    async fn test_nonce_validation() {
551        let storage = Arc::new(MemoryStorage::new());
552        let nonce_mgr = NonceManager::new(storage, 60);
553
554        let nonce = nonce_mgr.generate();
555
556        // First validation should succeed
557        assert!(nonce_mgr.validate(&nonce).await.unwrap());
558
559        // Store the nonce
560        nonce_mgr.store(&nonce, "user_123").await.unwrap();
561
562        // Second validation should fail (already used)
563        assert!(!nonce_mgr.validate(&nonce).await.unwrap());
564    }
565
566    #[tokio::test]
567    async fn test_nonce_validate_and_consume() {
568        let storage = Arc::new(MemoryStorage::new());
569        let nonce_mgr = NonceManager::new(storage, 60);
570
571        let nonce = nonce_mgr.generate();
572
573        // First use should succeed
574        nonce_mgr.validate_and_consume(&nonce, "user_123").await.unwrap();
575
576        // Second use should fail
577        let result = nonce_mgr.validate_and_consume(&nonce, "user_123").await;
578        assert!(result.is_err());
579    }
580
581    #[tokio::test]
582    async fn test_nonce_timestamp_check() {
583        let storage = Arc::new(MemoryStorage::new());
584        let nonce_mgr = NonceManager::new(storage, 60);
585
586        let nonce = nonce_mgr.generate();
587
588        // Should be within 60 seconds
589        assert!(nonce_mgr.check_timestamp(&nonce, 60).unwrap());
590
591        // Should also be within 1 second
592        assert!(nonce_mgr.check_timestamp(&nonce, 1).unwrap());
593    }
594}
595