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}