clamber_core/token/
mod.rs

1use crate::error::{ClamberError, Result};
2use chrono::{Duration, Utc};
3use hmac::{Hmac, Mac};
4use jwt::{SignWithKey, VerifyWithKey};
5use serde::{Serialize, de::DeserializeOwned};
6use sha2::Sha256;
7use std::collections::BTreeMap;
8
9/// JWT配置结构
10#[derive(Debug, Clone)]
11pub struct JwtConfig {
12    /// JWT密钥
13    pub secret: String,
14    /// 过期时间(天数)
15    pub expire_days: i64,
16}
17
18impl Default for JwtConfig {
19    fn default() -> Self {
20        Self {
21            secret: "default_jwt_secret".to_string(),
22            expire_days: 7,
23        }
24    }
25}
26
27impl JwtConfig {
28    /// 创建新的JWT配置
29    pub fn new(secret: impl Into<String>, expire_days: i64) -> Self {
30        Self {
31            secret: secret.into(),
32            expire_days,
33        }
34    }
35}
36
37/// JWT管理器
38pub struct JwtManager {
39    config: JwtConfig,
40}
41
42impl JwtManager {
43    /// 创建新的JWT管理器
44    pub fn new(config: JwtConfig) -> Self {
45        Self { config }
46    }
47
48    /// 使用默认配置创建JWT管理器
49    pub fn default() -> Self {
50        Self {
51            config: JwtConfig::default(),
52        }
53    }
54
55    /// 生成JWT token
56    pub fn generate_token<T>(&self, payload: &T) -> Result<String>
57    where
58        T: Serialize,
59    {
60        let expire_time = Utc::now() + Duration::days(self.config.expire_days);
61
62        // 将payload序列化为JSON字符串
63        let payload_json = serde_json::to_string(payload)?;
64
65        let mut claims = BTreeMap::new();
66        claims.insert("payload".to_string(), payload_json);
67        claims.insert("exp".to_string(), expire_time.timestamp().to_string());
68        claims.insert("createAt".to_string(), Utc::now().timestamp().to_string());
69
70        let key: Hmac<Sha256> =
71            Hmac::new_from_slice(self.config.secret.as_bytes()).map_err(|e| {
72                ClamberError::JwtKeyError {
73                    details: e.to_string(),
74                }
75            })?;
76
77        claims
78            .sign_with_key(&key)
79            .map_err(|e| ClamberError::JwtSignError {
80                details: e.to_string(),
81            })
82    }
83
84    /// 验证并解析JWT token
85    pub fn verify_token<T>(&self, token: &str) -> Result<T>
86    where
87        T: DeserializeOwned,
88    {
89        let key: Hmac<Sha256> =
90            Hmac::new_from_slice(self.config.secret.as_bytes()).map_err(|e| {
91                ClamberError::JwtKeyError {
92                    details: e.to_string(),
93                }
94            })?;
95
96        let claims: BTreeMap<String, String> =
97            token
98                .verify_with_key(&key)
99                .map_err(|e| ClamberError::JwtVerifyError {
100                    details: e.to_string(),
101                })?;
102
103        // 检查过期时间
104        if let Some(exp_str) = claims.get("exp") {
105            let exp_timestamp = exp_str.parse::<i64>().map_err(|_| ClamberError::JwtError {
106                message: "无效的过期时间格式".to_string(),
107            })?;
108
109            if exp_timestamp <= Utc::now().timestamp() {
110                return Err(ClamberError::JwtExpiredError);
111            }
112        } else {
113            return Err(ClamberError::JwtMissingFieldError {
114                field: "exp".to_string(),
115            });
116        }
117
118        // 获取payload并反序列化
119        if let Some(payload_str) = claims.get("payload") {
120            serde_json::from_str::<T>(payload_str).map_err(|e| ClamberError::DeserializationError {
121                details: e.to_string(),
122            })
123        } else {
124            Err(ClamberError::JwtMissingFieldError {
125                field: "payload".to_string(),
126            })
127        }
128    }
129
130    /// 检查token是否有效(不解析payload)
131    pub fn is_valid_token(&self, token: &str) -> bool {
132        let key = match Hmac::<Sha256>::new_from_slice(self.config.secret.as_bytes()) {
133            Ok(key) => key,
134            Err(_) => return false,
135        };
136
137        if let Ok(claims) = token.verify_with_key(&key) {
138            let claims: BTreeMap<String, String> = claims;
139            if let Some(exp_str) = claims.get("exp") {
140                if let Ok(exp_timestamp) = exp_str.parse::<i64>() {
141                    return exp_timestamp > Utc::now().timestamp();
142                }
143            }
144        }
145        false
146    }
147}
148
149// 便利函数:使用默认配置
150pub fn generate_token<T>(payload: &T, config: JwtConfig) -> Result<String>
151where
152    T: Serialize,
153{
154    let manager = JwtManager::new(config);
155    manager.generate_token(payload)
156}
157
158pub fn verify_token<T>(token: &str) -> Result<T>
159where
160    T: DeserializeOwned,
161{
162    let manager = JwtManager::default();
163    manager.verify_token(token)
164}
165
166pub fn is_valid_token(token: &str) -> bool {
167    let manager = JwtManager::default();
168    manager.is_valid_token(token)
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use serde::{Deserialize, Serialize};
175
176    #[derive(Debug, Serialize, Deserialize, PartialEq)]
177    struct TestUser {
178        pub id: String,
179        pub name: String,
180        pub role: String,
181    }
182
183    #[test]
184    fn test_jwt_generate_and_verify() {
185        let config = JwtConfig::new("test_secret", 1);
186        let manager = JwtManager::new(config);
187
188        let user = TestUser {
189            id: "123".to_string(),
190            name: "John Doe".to_string(),
191            role: "admin".to_string(),
192        };
193
194        // 生成token
195        let token = manager.generate_token(&user).unwrap();
196        assert!(!token.is_empty());
197
198        // 验证token
199        let decoded_user: TestUser = manager.verify_token(&token).unwrap();
200        assert_eq!(user, decoded_user);
201
202        // 检查token有效性
203        assert!(manager.is_valid_token(&token));
204    }
205
206    #[test]
207    fn test_convenience_functions() {
208        let user = TestUser {
209            id: "456".to_string(),
210            name: "Jane Doe".to_string(),
211            role: "user".to_string(),
212        };
213
214        let token = generate_token(&user, JwtConfig::default()).unwrap();
215        let decoded_user: TestUser = verify_token(&token).unwrap();
216
217        assert_eq!(user, decoded_user);
218        assert!(is_valid_token(&token));
219    }
220
221    #[test]
222    fn test_invalid_token() {
223        let manager = JwtManager::default();
224
225        // 测试无效token
226        assert!(!manager.is_valid_token("invalid_token"));
227
228        // 测试错误密钥
229        let config1 = JwtConfig::new("secret1", 1);
230        let config2 = JwtConfig::new("secret2", 1);
231        let manager1 = JwtManager::new(config1);
232        let manager2 = JwtManager::new(config2);
233
234        let user = TestUser {
235            id: "789".to_string(),
236            name: "Test User".to_string(),
237            role: "test".to_string(),
238        };
239
240        let token = manager1.generate_token(&user).unwrap();
241        assert!(manager2.verify_token::<TestUser>(&token).is_err());
242    }
243}