r_token/
lib.rs

1//! # r-token 🦀
2//!
3//! **r-token** is a lightweight, non-invasive authentication library designed for Rust (`actix-web`).
4//!
5//! **r-token** 是一个专为 Rust (`actix-web`) 设计的轻量级、无侵入式鉴权库。
6//!
7//! ## Design Philosophy | 设计理念
8//!
9//! Inspired by Java's [Sa-Token](https://sa-token.cc/), r-token provides an "out-of-the-box",
10//! "parameter-as-authentication" minimalist experience.
11//!
12//! 设计灵感来源于 Java 的 [Sa-Token](https://sa-token.cc/),旨在提供一种"开箱即用"、"参数即鉴权"的极简体验。
13//!
14//! ## Features | 特性
15//!
16//! - **Minimal Integration | 极简集成**: Initialize with just a few lines of code | 只需几行代码即可初始化
17//! - **Idiomatic Rust | Rust 风格**: Leverages Actix's `Extractor` mechanism, eliminating cumbersome `if/else` checks | 利用 Actix 的 `Extractor` 机制,摆脱繁琐的 `if/else` 检查
18//! - **Non-invasive | 零侵入**: Automatic authentication by declaring `RUser` in handler parameters | 在 Handler 参数中声明 `RUser` 即可自动完成鉴权
19//! - **State Sharing | 状态共享**: Thread-safe token management with `Arc` and `Mutex` | 基于 `Arc` 和 `Mutex` 实现线程安全的 Token 管理
20//!
21//! ## Quick Start | 快速开始
22//!
23//! ```rust,no_run
24//! use actix_web::{get, post, web, HttpResponse, HttpServer, App};
25//! use r_token::{RTokenManager, RUser};
26//!
27//! // Login endpoint | 登录接口
28//! #[post("/login")]
29//! async fn login(manager: web::Data<RTokenManager>) -> impl actix_web::Responder {
30//!     let user_id = "10086";
31//!     let token = manager.login(user_id);
32//!     HttpResponse::Ok().body(format!("Login Success, Token: {}", token))
33//! }
34//!
35//! // Protected endpoint - Users without valid tokens can't access! | 受保护接口 - 没有有效 Token 的用户无法访问!
36//! #[get("/info")]
37//! async fn user_info(user: RUser) -> impl actix_web::Responder {
38//!     format!("Hello, User ID: {}", user.id)
39//! }
40//!
41//! // Logout endpoint | 注销接口
42//! #[post("/logout")]
43//! async fn logout(manager: web::Data<RTokenManager>, user: RUser) -> impl actix_web::Responder {
44//!     manager.logout(&user.token);
45//!     HttpResponse::Ok().body("Logout Success")
46//! }
47//!
48//! #[actix_web::main]
49//! async fn main() -> std::io::Result<()> {
50//!     let manager = RTokenManager::new();
51//!     
52//!     HttpServer::new(move || {
53//!         App::new()
54//!             .app_data(web::Data::new(manager.clone()))
55//!             .service(login)
56//!             .service(user_info)
57//!             .service(logout)
58//!     })
59//!     .bind(("127.0.0.1", 8080))?
60//!     .run()
61//!     .await
62//! }
63//! ```
64
65use std::{collections::HashMap, sync::{Arc,Mutex}};
66use actix_web::{FromRequest, HttpRequest, web};
67use std::future::{ready, Ready};
68
69/// Token Manager | Token 管理器
70///
71/// `RTokenManager` is the core component of r-token library, responsible for managing user token lifecycle.
72///
73/// `RTokenManager` 是 r-token 库的核心组件,负责管理用户的 Token 生命周期。
74///
75/// ## Features | 特点
76///
77/// - **Thread-safe | 线程安全**: Safe multi-threaded access with `Arc<Mutex<HashMap>>` | 使用 `Arc<Mutex<HashMap>>` 实现多线程环境下的安全访问
78/// - **Cloneable | 可克隆**: Implements `Clone` trait for sharing across `actix-web` workers | 实现了 `Clone` trait,可以在多个 `actix-web` worker 之间共享
79/// - **Simple | 简单易用**: Provides two core methods: `login` and `logout` | 提供 `login` 和 `logout` 两个核心方法
80///
81/// ## Example | 示例
82///
83/// ```rust
84/// use r_token::RTokenManager;
85///
86/// let manager = RTokenManager::new();
87/// let token = manager.login("user123");
88/// println!("Generated token: {}", token);
89///
90/// // Later... | 稍后...
91/// manager.logout(&token);
92/// ```
93#[derive(Clone)]
94pub struct RTokenManager {
95    /// Internal storage: Key = Token, Value = User ID | 内部存储:Key = Token, Value = User ID
96    ///
97    /// Uses `Arc<Mutex<HashMap>>` to ensure thread-safety and shared ownership.
98    ///
99    /// 使用 `Arc<Mutex<HashMap>>` 确保线程安全和多所有权。
100    store: Arc<Mutex<HashMap<String, String>>>,
101}
102
103impl RTokenManager {
104    /// Create a new Token Manager instance | 创建一个新的 Token 管理器实例
105    ///
106    /// This method initializes an empty token storage. In an `actix-web` application,
107    /// it's typically called once in the `main` function, then injected into the app via `app_data`.
108    ///
109    /// 这个方法会初始化一个空的 Token 存储。在 `actix-web` 应用中,
110    /// 通常在 `main` 函数中调用一次,然后通过 `app_data` 注入到应用中。
111    ///
112    /// # Example | 示例
113    ///
114    /// ```rust
115    /// use r_token::RTokenManager;
116    /// use actix_web::{web, App};
117    ///
118    /// let manager = RTokenManager::new();
119    /// // Usage in actix-web | 在 actix-web 中使用
120    /// // App::new().app_data(web::Data::new(manager.clone()))
121    /// ```
122    pub fn new() -> Self {
123        Self {
124            store: Arc::new(Mutex::new(HashMap::new())),
125        }
126    }
127
128    /// User login: Generate and store Token | 用户登录:生成 Token 并存储
129    ///
130    /// This method will: | 此方法会:
131    /// 1. Generate a new UUID v4 as Token | 生成一个新的 UUID v4 作为 Token
132    /// 2. Store the mapping between Token and User ID in memory | 将 Token 和用户 ID 的映射关系存入内存
133    /// 3. Return the generated Token string | 返回生成的 Token 字符串
134    ///
135    /// # Parameters | 参数
136    ///
137    /// - `id`: User's unique identifier (usually user ID) | 用户的唯一标识符(通常是用户 ID)
138    ///
139    /// # Returns | 返回值
140    ///
141    /// Returns a newly generated Token string (UUID v4 format) | 返回一个新生成的 Token 字符串(UUID v4 格式)
142    ///
143    /// # Example | 示例
144    ///
145    /// ```rust
146    /// use r_token::RTokenManager;
147    ///
148    /// let manager = RTokenManager::new();
149    /// let token = manager.login("user123");
150    /// assert!(!token.is_empty());
151    /// ```
152    pub fn login(&self,id:&str) -> String {
153        let token = uuid::Uuid::new_v4().to_string();
154        self.store.lock().unwrap().insert(token.clone(), id.to_string());
155        token
156    }
157
158    /// User logout: Remove Token | 用户登出:移除 Token
159    ///
160    /// This method removes the specified Token from memory, invalidating it.
161    /// Invalidated tokens will fail validation through the `RUser` extractor.
162    ///
163    /// 此方法会从内存中删除指定的 Token,使其失效。
164    /// 失效后的 Token 将无法通过 `RUser` extractor 的验证。
165    ///
166    /// # Parameters | 参数
167    ///
168    /// - `token`: The Token string to invalidate | 要注销的 Token 字符串
169    ///
170    /// # Example | 示例
171    ///
172    /// ```rust
173    /// use r_token::RTokenManager;
174    ///
175    /// let manager = RTokenManager::new();
176    /// let token = manager.login("user123");
177    ///
178    /// // User logout | 用户登出
179    /// manager.logout(&token);
180    /// // Token is now invalid | 此时 token 已失效
181    /// ```
182    pub fn logout(&self, token: &str) {
183        self.store.lock().unwrap().remove(token);
184    }
185
186}
187
188/// Authenticated User Information | 已认证用户信息
189///
190/// `RUser` is the core concept of r-token. It implements `actix-web`'s `FromRequest` trait,
191/// enabling "parameter-as-authentication" by using it directly as a handler parameter.
192///
193/// `RUser` 是 r-token 最核心的概念,它实现了 `actix-web` 的 `FromRequest` trait,
194/// 可以作为 Handler 的参数直接使用,实现"参数即鉴权"的效果。
195///
196/// ## How It Works | 工作原理
197///
198/// When you declare `RUser` as a handler parameter, `actix-web` automatically:
199///
200/// 当你在 Handler 参数中声明 `RUser` 时,`actix-web` 会自动:
201///
202/// 1. Extracts the Token from the `Authorization` header | 从请求的 `Authorization` header 中提取 Token
203/// 2. Validates the Token through `RTokenManager` | 通过 `RTokenManager` 验证 Token 的有效性
204/// 3. If valid, creates an `RUser` instance and passes it to your handler | 如果验证通过,创建 `RUser` 实例并传递给你的 Handler
205/// 4. If invalid, returns 401 Unauthorized without calling the handler | 如果验证失败,直接返回 401 Unauthorized,Handler 不会被调用
206///
207/// ## Zero-Intrusion Design | 零侵入式设计
208///
209/// You don't need any `if/else` checks in your business code to verify if a user is logged in.
210/// If a parameter has `RUser`, the user is guaranteed to be authenticated!
211///
212/// 你不需要在业务代码中写任何 `if/else` 来检查用户是否登录,
213/// 只要参数里有 `RUser`,就保证用户一定是已登录的!
214///
215/// ## Example | 示例
216///
217/// ```rust,no_run
218/// use actix_web::{get, HttpResponse};
219/// use r_token::RUser;
220///
221/// #[get("/protected")]
222/// async fn protected_route(user: RUser) -> impl actix_web::Responder {
223///     // If we get here, user is guaranteed to be valid! | 能进到这里,user 一定是合法的!
224///     HttpResponse::Ok().body(format!("Welcome, user {}", user.id))
225/// }
226/// ```
227#[derive(Debug)]
228pub struct RUser {
229    /// User ID | 用户 ID
230    ///
231    /// Corresponds to the user identifier passed during login | 对应登录时传入的用户标识符
232    pub id: String,
233    
234    /// User's Token | 用户的 Token
235    ///
236    /// The Token string extracted from the `Authorization` header | 从 `Authorization` header 中提取的 Token 字符串
237    pub token: String,
238}
239
240/// `FromRequest` Trait Implementation | `FromRequest` trait 实现
241///
242/// This is the key to r-token's "parameter-as-authentication" feature.
243///
244/// 这是 r-token 实现"参数即鉴权"的关键。
245///
246/// ## Execution Flow | 执行流程
247///
248/// When `actix-web` receives a request and finds a handler needs an `RUser` parameter,
249/// it automatically executes this logic:
250///
251/// 当 `actix-web` 收到请求并发现 Handler 需要 `RUser` 参数时,会自动执行这里的逻辑:
252///
253/// 1. **Get Token Manager | 获取 Token 管理器**: Extract `RTokenManager` from `app_data` | 从 `app_data` 中提取 `RTokenManager`
254/// 2. **Extract Token | 提取 Token**: Get Token from `Authorization` header (supports `Bearer` prefix) | 从 `Authorization` header 中获取 Token(支持 `Bearer` 前缀)
255/// 3. **Validate Token | 验证 Token**: Check if Token exists in manager's storage | 检查 Token 是否存在于管理器的存储中
256/// 4. **Return Result | 返回结果**:
257///    - Success → Create `RUser` instance, handler executes normally | 成功 → 创建 `RUser` 实例,Handler 正常执行
258///    - Failure → Return 401 Unauthorized, handler is not called | 失败 → 返回 401 Unauthorized,Handler 不会被调用
259///
260/// ## Error Handling | 错误处理
261///
262/// - `500 Internal Server Error`: Token manager not injected into `app_data` | Token 管理器未注入到 `app_data`
263/// - `401 Unauthorized`: Token missing or invalid | Token 缺失或无效
264impl FromRequest for RUser {
265    type Error = actix_web::Error;
266    type Future = Ready<Result<Self, Self::Error>>;
267
268    fn from_request(req: &HttpRequest,_payload: &mut actix_web::dev::Payload) -> Self::Future {
269
270        // 獲取管理器
271        let manager = match req.app_data::<web::Data<RTokenManager>>() {
272            Some(m) => m,
273            None => return ready(Err(actix_web::error::ErrorInternalServerError("Token manager not found"))),
274        };
275        // 獲取Token(優先看header中的Authorization)
276        let token = match req.headers().get("Authorization").and_then(|h| h.to_str().ok()) {
277            Some(token_str) => token_str.strip_prefix("Bearer ").unwrap_or(token_str).to_string(),
278            None => return ready(Err(actix_web::error::ErrorUnauthorized("Unauthorized"))),
279        };
280
281        // 驗證token
282        let store = manager.store.lock().unwrap();
283        match store.get(&token) {
284            Some(id) => {
285                return ready(Ok(RUser { id: id.clone(), token: token.clone() }));
286            }
287            None => {
288                return ready(Err(actix_web::error::ErrorUnauthorized("Invalid token")));
289            }
290        }
291    }
292
293    
294}
295
296// ============ 单元测试 ============
297#[cfg(test)]
298mod unit_tests {
299    use super::*;
300
301    #[test]
302    fn test_login() {
303        let manager = RTokenManager::new();
304        let token = manager.login("user123");
305        
306        // 验证 token 不为空
307        assert!(!token.is_empty());
308        
309        // 验证 token 是有效的 UUID 格式
310        assert!(uuid::Uuid::parse_str(&token).is_ok());
311    }
312
313    #[test]
314    fn test_logout() {
315        let manager = RTokenManager::new();
316        let token = manager.login("user456");
317        
318        // 登出前,token 应该存在
319        assert!(manager.store.lock().unwrap().contains_key(&token));
320        
321        // 登出
322        manager.logout(&token);
323        
324        // 登出后,token 应该被移除
325        assert!(!manager.store.lock().unwrap().contains_key(&token));
326    }
327
328    #[test]
329    fn test_multiple_users() {
330        let manager = RTokenManager::new();
331        
332        let token1 = manager.login("user1");
333        let token2 = manager.login("user2");
334        let token3 = manager.login("user3");
335        
336        // 验证三个 token 都不同
337        assert_ne!(token1, token2);
338        assert_ne!(token2, token3);
339        assert_ne!(token1, token3);
340        
341        // 验证所有 token 都存在
342        let store = manager.store.lock().unwrap();
343        assert_eq!(store.len(), 3);
344        assert_eq!(store.get(&token1), Some(&"user1".to_string()));
345        assert_eq!(store.get(&token2), Some(&"user2".to_string()));
346        assert_eq!(store.get(&token3), Some(&"user3".to_string()));
347    }
348}
349
350// ============ 集成测试 ============
351#[cfg(test)]
352mod integration_tests {
353    use super::*;
354    use actix_web::{test, web, App, http::header, HttpResponse};
355
356    #[actix_web::test]
357    async fn test_from_request_valid_token() {
358        let manager = RTokenManager::new();
359        let token = manager.login("test_user");
360        
361        // 创建测试 app
362        let app = test::init_service(
363            App::new()
364                .app_data(web::Data::new(manager.clone()))
365                .route("/test", web::get().to(|user: RUser| async move {
366                    HttpResponse::Ok().body(format!("User ID: {}", user.id))
367                }))
368        ).await;
369        
370        // 发送带有 Authorization header 的请求
371        let req = test::TestRequest::get()
372            .uri("/test")
373            .insert_header((header::AUTHORIZATION, format!("Bearer {}", token)))
374            .to_request();
375        
376        let resp = test::call_service(&app, req).await;
377        assert!(resp.status().is_success());
378    }
379
380    #[actix_web::test]
381    async fn test_from_request_missing_token() {
382        let manager = RTokenManager::new();
383        
384        let app = test::init_service(
385            App::new()
386                .app_data(web::Data::new(manager.clone()))
387                .route("/test", web::get().to(|user: RUser| async move {
388                    HttpResponse::Ok().body(format!("User ID: {}", user.id))
389                }))
390        ).await;
391        
392        // 发送没有 Authorization header 的请求
393        let req = test::TestRequest::get()
394            .uri("/test")
395            .to_request();
396        
397        let resp = test::call_service(&app, req).await;
398        assert_eq!(resp.status(), 401); // Unauthorized
399    }
400
401    #[actix_web::test]
402    async fn test_from_request_invalid_token() {
403        let manager = RTokenManager::new();
404        
405        let app = test::init_service(
406            App::new()
407                .app_data(web::Data::new(manager.clone()))
408                .route("/test", web::get().to(|user: RUser| async move {
409                    HttpResponse::Ok().body(format!("User ID: {}", user.id))
410                }))
411        ).await;
412        
413        // 发送带有无效 token 的请求
414        let req = test::TestRequest::get()
415            .uri("/test")
416            .insert_header((header::AUTHORIZATION, "Bearer invalid-token-12345"))
417            .to_request();
418        
419        let resp = test::call_service(&app, req).await;
420        assert_eq!(resp.status(), 401); // Unauthorized
421    }
422
423    #[actix_web::test]
424    async fn test_logout_invalidates_token() {
425        let manager = RTokenManager::new();
426        let token = manager.login("test_user");
427        
428        let app = test::init_service(
429            App::new()
430                .app_data(web::Data::new(manager.clone()))
431                .route("/test", web::get().to(|user: RUser| async move {
432                    HttpResponse::Ok().body(format!("User ID: {}", user.id))
433                }))
434        ).await;
435        
436        // 第一次请求应该成功
437        let req = test::TestRequest::get()
438            .uri("/test")
439            .insert_header((header::AUTHORIZATION, format!("Bearer {}", token)))
440            .to_request();
441        
442        let resp = test::call_service(&app, req).await;
443        assert!(resp.status().is_success());
444        
445        // 登出
446        manager.logout(&token);
447        
448        // 第二次请求应该失败(token 已失效)
449        let req = test::TestRequest::get()
450            .uri("/test")
451            .insert_header((header::AUTHORIZATION, format!("Bearer {}", token)))
452            .to_request();
453        
454        let resp = test::call_service(&app, req).await;
455        assert_eq!(resp.status(), 401); // Unauthorized
456    }
457}