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