Skip to main content

sa_token_core/token/
jwt.rs

1// Author: 金书记
2//
3//! JWT (JSON Web Token) Module | JWT (JSON Web Token) 模块
4//!
5//! ## 模式说明(对齐 Java StpLogicJwt)
6//!
7//! 当前 Rust 实现为 **Simple 风格**:`token` 本身是 JWT,但 Session / 权限等仍走 [`SaStorage`]。
8//! Stateless(无状态 Session)与 Mixin(混合映射)模式尚未全量移植;logout 仍会清理 storage 中的 token 映射。
9//!
10//! JWT 生成失败时见 [`SaTokenConfig::jwt_fallback_on_error`]:默认回退 UUID 并 `tracing::warn`,可关闭回退以快速失败。
11//!
12//! Provides complete JWT functionality including generation, validation, and parsing.
13//! 提供完整的 JWT 功能,包括生成、验证和解析。
14//!
15//! ## Features | 功能特性
16//!
17//! - Multiple algorithms support (HS256, HS384, HS512, RS256, etc.)
18//!   支持多种算法(HS256, HS384, HS512, RS256 等)
19//! - Custom claims support | 支持自定义声明
20//! - Expiration time validation | 过期时间验证
21//! - Token refresh | Token 刷新
22//!
23//! ## Usage Example | 使用示例
24//!
25//! ```rust,ignore
26//! use sa_token_core::token::jwt::{JwtManager, JwtClaims};
27//!
28//! // Create JWT manager | 创建 JWT 管理器
29//! let jwt_manager = JwtManager::new("your-secret-key");
30//!
31//! // Generate JWT token | 生成 JWT token
32//! let mut claims = JwtClaims::new("user_123");
33//! claims.set_expiration(3600); // 1 hour | 1小时
34//! let token = jwt_manager.generate(&claims)?;
35//!
36//! // Validate and parse JWT token | 验证并解析 JWT token
37//! let decoded_claims = jwt_manager.validate(&token)?;
38//! println!("User ID: {}", decoded_claims.login_id);
39//! ```
40
41use chrono::{DateTime, Duration, Utc};
42use jsonwebtoken::{
43    decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation,
44};
45use serde::{Deserialize, Serialize};
46use serde_json::Value;
47use std::collections::HashMap;
48
49use crate::error::{SaTokenError, SaTokenResult};
50
51/// JWT Algorithm | JWT 算法
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53#[derive(Default)]
54pub enum JwtAlgorithm {
55    /// HMAC using SHA-256 | 使用 SHA-256 的 HMAC
56    #[default]
57    HS256,
58    /// HMAC using SHA-384 | 使用 SHA-384 的 HMAC
59    HS384,
60    /// HMAC using SHA-512 | 使用 SHA-512 的 HMAC
61    HS512,
62    /// RSA using SHA-256 | 使用 SHA-256 的 RSA
63    RS256,
64    /// RSA using SHA-384 | 使用 SHA-384 的 RSA
65    RS384,
66    /// RSA using SHA-512 | 使用 SHA-512 的 RSA
67    RS512,
68    /// ECDSA using SHA-256 | 使用 SHA-256 的 ECDSA
69    ES256,
70    /// ECDSA using SHA-384 | 使用 SHA-384 的 ECDSA
71    ES384,
72}
73
74
75impl From<JwtAlgorithm> for Algorithm {
76    fn from(alg: JwtAlgorithm) -> Self {
77        match alg {
78            JwtAlgorithm::HS256 => Algorithm::HS256,
79            JwtAlgorithm::HS384 => Algorithm::HS384,
80            JwtAlgorithm::HS512 => Algorithm::HS512,
81            JwtAlgorithm::RS256 => Algorithm::RS256,
82            JwtAlgorithm::RS384 => Algorithm::RS384,
83            JwtAlgorithm::RS512 => Algorithm::RS512,
84            JwtAlgorithm::ES256 => Algorithm::ES256,
85            JwtAlgorithm::ES384 => Algorithm::ES384,
86        }
87    }
88}
89
90/// JWT Claims | JWT 声明
91///
92/// Standard JWT claims with sa-token extensions
93/// 标准 JWT 声明及 sa-token 扩展
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct JwtClaims {
96    /// Subject (user identifier) | 主题(用户标识符)
97    #[serde(rename = "sub")]
98    pub login_id: String,
99
100    /// Issuer | 签发者
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub iss: Option<String>,
103
104    /// Audience | 受众
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub aud: Option<String>,
107
108    /// Expiration time (Unix timestamp) | 过期时间(Unix 时间戳)
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub exp: Option<i64>,
111
112    /// Not before time (Unix timestamp) | 生效时间(Unix 时间戳)
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub nbf: Option<i64>,
115
116    /// Issued at time (Unix timestamp) | 签发时间(Unix 时间戳)
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub iat: Option<i64>,
119
120    /// JWT ID (unique identifier) | JWT ID(唯一标识符)
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub jti: Option<String>,
123
124    // Sa-token extensions | Sa-token 扩展字段
125
126    /// Login type (user, admin, etc.) | 登录类型(用户、管理员等)
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub login_type: Option<String>,
129
130    /// Device identifier | 设备标识
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub device: Option<String>,
133
134    /// Custom data | 自定义数据
135    #[serde(default)]
136    #[serde(skip_serializing_if = "HashMap::is_empty")]
137    pub extra: HashMap<String, Value>,
138}
139
140impl JwtClaims {
141    /// Create new JWT claims | 创建新的 JWT 声明
142    ///
143    /// # Arguments | 参数
144    ///
145    /// * `login_id` - User identifier | 用户标识符
146    pub fn new(login_id: impl Into<String>) -> Self {
147        let now = Utc::now().timestamp();
148        Self {
149            login_id: login_id.into(),
150            iss: None,
151            aud: None,
152            exp: None,
153            nbf: None,
154            iat: Some(now),
155            jti: None,
156            login_type: Some("default".to_string()),
157            device: None,
158            extra: HashMap::new(),
159        }
160    }
161
162    /// Set expiration time in seconds from now | 设置从现在开始的过期时间(秒)
163    ///
164    /// # Arguments | 参数
165    ///
166    /// * `seconds` - Seconds until expiration | 到期秒数
167    pub fn set_expiration(&mut self, seconds: i64) -> &mut Self {
168        let exp_time = Utc::now() + Duration::seconds(seconds);
169        self.exp = Some(exp_time.timestamp());
170        self
171    }
172
173    /// Set expiration at specific time | 设置具体的过期时间
174    pub fn set_expiration_at(&mut self, datetime: DateTime<Utc>) -> &mut Self {
175        self.exp = Some(datetime.timestamp());
176        self
177    }
178
179    /// Set issuer | 设置签发者
180    pub fn set_issuer(&mut self, issuer: impl Into<String>) -> &mut Self {
181        self.iss = Some(issuer.into());
182        self
183    }
184
185    /// Set audience | 设置受众
186    pub fn set_audience(&mut self, audience: impl Into<String>) -> &mut Self {
187        self.aud = Some(audience.into());
188        self
189    }
190
191    /// Set JWT ID | 设置 JWT ID
192    pub fn set_jti(&mut self, jti: impl Into<String>) -> &mut Self {
193        self.jti = Some(jti.into());
194        self
195    }
196
197    /// Set login type | 设置登录类型
198    pub fn set_login_type(&mut self, login_type: impl Into<String>) -> &mut Self {
199        self.login_type = Some(login_type.into());
200        self
201    }
202
203    /// Set device identifier | 设置设备标识
204    pub fn set_device(&mut self, device: impl Into<String>) -> &mut Self {
205        self.device = Some(device.into());
206        self
207    }
208
209    /// Add custom claim | 添加自定义声明
210    pub fn add_claim(&mut self, key: impl Into<String>, value: Value) -> &mut Self {
211        self.extra.insert(key.into(), value);
212        self
213    }
214
215    /// Get custom claim | 获取自定义声明
216    pub fn get_claim(&self, key: &str) -> Option<&Value> {
217        self.extra.get(key)
218    }
219    
220    /// Set all custom claims at once | 一次设置所有自定义声明
221    pub fn set_claims(&mut self, claims: HashMap<String, Value>) -> &mut Self {
222        self.extra = claims;
223        self
224    }
225    
226    /// Get all custom claims | 获取所有自定义声明
227    pub fn get_claims(&self) -> &HashMap<String, Value> {
228        &self.extra
229    }
230
231    /// Check if token is expired | 检查 token 是否过期
232    pub fn is_expired(&self) -> bool {
233        if let Some(exp) = self.exp {
234            let now = Utc::now().timestamp();
235            now >= exp
236        } else {
237            false
238        }
239    }
240
241    /// Get remaining time in seconds | 获取剩余时间(秒)
242    pub fn remaining_time(&self) -> Option<i64> {
243        self.exp.map(|exp| {
244            let now = Utc::now().timestamp();
245            (exp - now).max(0)
246        })
247    }
248}
249
250/// JWT Manager | JWT 管理器
251///
252/// Manages JWT token generation, validation, and parsing
253/// 管理 JWT token 的生成、验证和解析
254#[derive(Clone)]
255pub struct JwtManager {
256    /// Secret key for HMAC algorithms | HMAC 算法的密钥
257    secret: String,
258
259    /// Algorithm to use | 使用的算法
260    algorithm: JwtAlgorithm,
261
262    /// Issuer | 签发者
263    issuer: Option<String>,
264
265    /// Audience | 受众
266    audience: Option<String>,
267}
268
269impl JwtManager {
270    /// Create new JWT manager with HS256 algorithm | 创建使用 HS256 算法的新 JWT 管理器
271    ///
272    /// # Arguments | 参数
273    ///
274    /// * `secret` - Secret key | 密钥
275    pub fn new(secret: impl Into<String>) -> Self {
276        Self {
277            secret: secret.into(),
278            algorithm: JwtAlgorithm::HS256,
279            issuer: None,
280            audience: None,
281        }
282    }
283
284    /// Create JWT manager with custom algorithm | 创建使用自定义算法的 JWT 管理器
285    pub fn with_algorithm(secret: impl Into<String>, algorithm: JwtAlgorithm) -> Self {
286        Self {
287            secret: secret.into(),
288            algorithm,
289            issuer: None,
290            audience: None,
291        }
292    }
293
294    /// Set issuer | 设置签发者
295    pub fn set_issuer(mut self, issuer: impl Into<String>) -> Self {
296        self.issuer = Some(issuer.into());
297        self
298    }
299
300    /// Set audience | 设置受众
301    pub fn set_audience(mut self, audience: impl Into<String>) -> Self {
302        self.audience = Some(audience.into());
303        self
304    }
305
306    /// Generate JWT token | 生成 JWT token
307    ///
308    /// # Arguments | 参数
309    ///
310    /// * `claims` - JWT claims | JWT 声明
311    ///
312    /// # Returns | 返回
313    ///
314    /// JWT token string | JWT token 字符串
315    pub fn generate(&self, claims: &JwtClaims) -> SaTokenResult<String> {
316        let mut final_claims = claims.clone();
317
318        // Set issuer and audience if configured
319        // 如果配置了签发者和受众,则设置
320        if self.issuer.is_some() && final_claims.iss.is_none() {
321            final_claims.iss = self.issuer.clone();
322        }
323        if self.audience.is_some() && final_claims.aud.is_none() {
324            final_claims.aud = self.audience.clone();
325        }
326
327        let header = Header::new(self.algorithm.into());
328        let encoding_key = EncodingKey::from_secret(self.secret.as_bytes());
329
330        encode(&header, &final_claims, &encoding_key).map_err(|e| {
331            SaTokenError::InvalidToken(format!("Failed to generate JWT: {}", e))
332        })
333    }
334
335    /// Validate and parse JWT token | 验证并解析 JWT token
336    ///
337    /// # Arguments | 参数
338    ///
339    /// * `token` - JWT token string | JWT token 字符串
340    ///
341    /// # Returns | 返回
342    ///
343    /// Decoded JWT claims | 解码的 JWT 声明
344    pub fn validate(&self, token: &str) -> SaTokenResult<JwtClaims> {
345        let mut validation = Validation::new(self.algorithm.into());
346
347        // Explicitly enable expiration validation | 明确启用过期验证
348        validation.validate_exp = true;
349        
350        // Set leeway to 0 for strict validation | 设置时间偏差为0以进行严格验证
351        validation.leeway = 0;
352
353        // Configure validation | 配置验证
354        if let Some(ref iss) = self.issuer {
355            validation.set_issuer(&[iss]);
356        }
357        if let Some(ref aud) = self.audience {
358            validation.set_audience(&[aud]);
359        }
360
361        let decoding_key = DecodingKey::from_secret(self.secret.as_bytes());
362
363        let token_data = decode::<JwtClaims>(token, &decoding_key, &validation).map_err(|e| {
364            match e.kind() {
365                jsonwebtoken::errors::ErrorKind::ExpiredSignature => {
366                    SaTokenError::TokenExpired
367                }
368                _ => SaTokenError::InvalidToken(format!("JWT validation failed: {}", e)),
369            }
370        })?;
371
372        Ok(token_data.claims)
373    }
374
375    /// Decode JWT without validation (unsafe) | 不验证解码 JWT(不安全)
376    ///
377    /// Warning: This does not validate the signature!
378    /// 警告:这不会验证签名!
379    pub fn decode_without_validation(&self, token: &str) -> SaTokenResult<JwtClaims> {
380        // jsonwebtoken 10.x 起 `Validation::insecure_disable_signature_validation` 已废弃,
381        // 官方推荐使用 `jsonwebtoken::dangerous::insecure_decode`,仅做编解码不校验签名/过期。
382        let token_data = jsonwebtoken::dangerous::insecure_decode::<JwtClaims>(token)
383            .map_err(|e| SaTokenError::InvalidToken(format!("Failed to decode JWT: {}", e)))?;
384
385        Ok(token_data.claims)
386    }
387
388    /// Refresh JWT token | 刷新 JWT token
389    ///
390    /// Creates a new token with updated expiration time
391    /// 创建具有更新过期时间的新 token
392    ///
393    /// # Arguments | 参数
394    ///
395    /// * `token` - Original JWT token | 原始 JWT token
396    /// * `extend_seconds` - Seconds to extend | 延长的秒数
397    pub fn refresh(&self, token: &str, extend_seconds: i64) -> SaTokenResult<String> {
398        let mut claims = self.validate(token)?;
399
400        // Update expiration time | 更新过期时间
401        claims.set_expiration(extend_seconds);
402
403        // Update issued at time | 更新签发时间
404        claims.iat = Some(Utc::now().timestamp());
405
406        self.generate(&claims)
407    }
408
409    /// Extract user ID from token without full validation | 从 token 提取用户 ID(无需完整验证)
410    ///
411    /// Useful for quick user identification
412    /// 用于快速用户识别
413    pub fn extract_login_id(&self, token: &str) -> SaTokenResult<String> {
414        let claims = self.decode_without_validation(token)?;
415        Ok(claims.login_id)
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    use super::*;
422
423    #[test]
424    fn test_jwt_claims_creation() {
425        let mut claims = JwtClaims::new("user_123");
426        claims.set_expiration(3600);
427        claims.set_issuer("sa-token");
428        claims.add_claim("role", serde_json::json!("admin"));
429
430        assert_eq!(claims.login_id, "user_123");
431        assert!(claims.exp.is_some());
432        assert_eq!(claims.iss, Some("sa-token".to_string()));
433        assert_eq!(
434            claims.get_claim("role"),
435            Some(&serde_json::json!("admin"))
436        );
437    }
438
439    #[test]
440    fn test_jwt_generate_and_validate() {
441        let jwt_manager = JwtManager::new("test-secret-key");
442
443        let mut claims = JwtClaims::new("user_123");
444        claims.set_expiration(3600);
445
446        // Generate token | 生成 token
447        let token = jwt_manager.generate(&claims).unwrap();
448        assert!(!token.is_empty());
449
450        // Validate token | 验证 token
451        let decoded = jwt_manager.validate(&token).unwrap();
452        assert_eq!(decoded.login_id, "user_123");
453        assert!(!decoded.is_expired());
454    }
455
456    #[test]
457    fn test_jwt_expired() {
458        let jwt_manager = JwtManager::new("test-secret-key");
459
460        let mut claims = JwtClaims::new("user_123");
461        // Set expiration to 10 seconds in the past to account for leeway
462        // 设置过期时间为10秒前以考虑时间偏差
463        let exp_time = Utc::now() - Duration::seconds(10);
464        claims.set_expiration_at(exp_time);
465
466        let token = jwt_manager.generate(&claims).unwrap();
467
468        // Should fail validation due to expiration | 应该因过期而验证失败
469        let result = jwt_manager.validate(&token);
470        assert!(result.is_err());
471        
472        // Verify it's specifically an expiration error | 验证是过期错误
473        match result {
474            Err(SaTokenError::TokenExpired) => {}, // Expected | 预期
475            _ => panic!("Expected TokenExpired error"),
476        }
477    }
478
479    #[test]
480    fn test_jwt_refresh() {
481        let jwt_manager = JwtManager::new("test-secret-key");
482
483        let mut claims = JwtClaims::new("user_123");
484        claims.set_expiration(3600);
485
486        let original_token = jwt_manager.generate(&claims).unwrap();
487
488        // Refresh token | 刷新 token
489        let new_token = jwt_manager.refresh(&original_token, 7200).unwrap();
490        assert_ne!(original_token, new_token);
491
492        // Validate new token | 验证新 token
493        let decoded = jwt_manager.validate(&new_token).unwrap();
494        assert_eq!(decoded.login_id, "user_123");
495    }
496
497    #[test]
498    fn test_jwt_custom_claims() {
499        let jwt_manager = JwtManager::new("test-secret-key");
500
501        let mut claims = JwtClaims::new("user_123");
502        claims.set_expiration(3600);
503        claims.add_claim("role", serde_json::json!("admin"));
504        claims.add_claim("permissions", serde_json::json!(["read", "write"]));
505
506        let token = jwt_manager.generate(&claims).unwrap();
507        let decoded = jwt_manager.validate(&token).unwrap();
508
509        assert_eq!(decoded.get_claim("role"), Some(&serde_json::json!("admin")));
510        assert_eq!(
511            decoded.get_claim("permissions"),
512            Some(&serde_json::json!(["read", "write"]))
513        );
514    }
515
516    #[test]
517    fn test_extract_login_id() {
518        let jwt_manager = JwtManager::new("test-secret-key");
519
520        let mut claims = JwtClaims::new("user_123");
521        claims.set_expiration(3600);
522
523        let token = jwt_manager.generate(&claims).unwrap();
524        let login_id = jwt_manager.extract_login_id(&token).unwrap();
525
526        assert_eq!(login_id, "user_123");
527    }
528}
529