Skip to main content

fbc_starter/auth/
jwt.rs

1/// JWT Token 验证服务(sa-token 兼容 Claims 结构)
2///
3/// Claims 结构与 sa-token `JwtClaims` 完全兼容:
4/// - `sub` — 用户 ID(login_id)
5/// - `exp` / `iat` / `jti` — 标准 JWT 字段
6/// - `extra` — 业务扩展字段(tenant_id, username, token_type 等)
7///
8/// **Token 生成**:由 ms-auth 使用 sa-token `JwtManager` 完成
9/// **Token 验证**:所有微服务使用此模块解码(仅需 `jsonwebtoken` crate)
10use jsonwebtoken::{decode, DecodingKey, Validation};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14/// JWT Claims(与 sa-token JwtClaims 兼容)
15///
16/// 网关和所有微服务共用此结构解码 JWT。
17/// ms-auth 通过 sa-token 的 `JwtClaims.extra` 注入 `tenant_id`、`username` 等。
18#[derive(Debug, Serialize, Deserialize, Clone)]
19pub struct Claims {
20    /// 用户 ID(sa-token 的 login_id)
21    #[serde(rename = "sub")]
22    pub login_id: String,
23
24    /// 过期时间(Unix 时间戳,秒)
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub exp: Option<i64>,
27
28    /// 签发时间(Unix 时间戳,秒)
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub iat: Option<i64>,
31
32    /// JWT ID(唯一标识符)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub jti: Option<String>,
35
36    /// 签发者
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub iss: Option<String>,
39
40    /// 受众
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub aud: Option<String>,
43
44    /// 登录类型
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub login_type: Option<String>,
47
48    /// 扩展字段(tenant_id, username, token_type 等业务字段)
49    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
50    pub extra: HashMap<String, serde_json::Value>,
51}
52
53impl Claims {
54    /// 获取用户 ID(i64)
55    pub fn user_id(&self) -> Option<i64> {
56        self.login_id.parse::<i64>().ok()
57    }
58
59    /// 获取租户 ID(从 extra 中读取)
60    pub fn tenant_id(&self) -> Option<i64> {
61        self.extra
62            .get("tenant_id")
63            .and_then(|v| v.as_str())
64            .and_then(|s| s.parse::<i64>().ok())
65    }
66
67    /// 获取用户名(从 extra 中读取)
68    pub fn username(&self) -> String {
69        self.extra
70            .get("username")
71            .and_then(|v| v.as_str())
72            .unwrap_or("")
73            .to_string()
74    }
75
76    /// 获取 token 类型(access / refresh,从 extra 中读取)
77    pub fn token_type(&self) -> String {
78        self.extra
79            .get("token_type")
80            .and_then(|v| v.as_str())
81            .unwrap_or("access")
82            .to_string()
83    }
84}
85
86/// JWT 验证服务(仅解码,不生成 — 生成由 ms-auth 的 sa-token 负责)
87pub struct JwtService {
88    decoding_key: DecodingKey,
89}
90
91impl JwtService {
92    /// 从环境变量 APP__JWT__SECRET 创建 JWT 验证服务
93    pub fn from_env() -> Result<Self, String> {
94        let secret = std::env::var("APP__JWT__SECRET")
95            .map_err(|_| "环境变量 APP__JWT__SECRET 未设置".to_string())?;
96
97        Ok(Self {
98            decoding_key: DecodingKey::from_secret(secret.as_bytes()),
99        })
100    }
101
102    /// 从密钥字符串创建 JWT 验证服务
103    pub fn new(secret: &str) -> Self {
104        Self {
105            decoding_key: DecodingKey::from_secret(secret.as_bytes()),
106        }
107    }
108
109    /// 验证并解析 Token
110    pub fn verify_token(&self, token: &str) -> Result<Claims, String> {
111        let token = token.trim();
112        // 去除 Bearer 前缀
113        let token = if token.starts_with("Bearer ") {
114            &token[7..]
115        } else {
116            token
117        };
118
119        let mut validation = Validation::default();
120        validation.leeway = 300; // 5 分钟容差
121
122        let token_data = decode::<Claims>(token, &self.decoding_key, &validation)
123            .map_err(|e| format!("JWT 验证失败: {}", e))?;
124
125        Ok(token_data.claims)
126    }
127}