Skip to main content

fbc_starter/auth/
context.rs

1use axum::{
2    extract::{FromRequestParts, Request},
3    http::{request::Parts, StatusCode},
4    middleware::Next,
5    response::{IntoResponse, Response},
6};
7
8/// 请求上下文(网关透传的用户信息)
9///
10/// 网关验证 JWT 后,将用户信息通过 HTTP 头透传到下游服务:
11/// - `X-User-Id`: 用户 ID
12/// - `X-Tenant-Id`: 租户 ID(可选)
13/// - `X-Username`: 用户名
14#[derive(Debug, Clone)]
15pub struct RequestContext {
16    /// 用户 ID
17    pub user_id: i64,
18    /// 租户 ID(可选,无租户时为 None)
19    pub tenant_id: Option<i64>,
20    /// 用户名
21    pub username: String,
22}
23
24/// 中间件:从网关透传的 X-User-* Header 中解析用户上下文
25///
26/// 将解析后的 `RequestContext` 注入到请求扩展中,
27/// 下游 handler 可通过 Axum extractor 直接使用。
28///
29/// 注意:如果请求头中没有 X-User-Id(白名单接口),
30/// 中间件仍然放行但不注入上下文。Handler 需要自行判断。
31pub async fn user_context_middleware(request: Request, next: Next) -> Response {
32    let mut request = request;
33
34    // 尝试从 header 解析用户上下文
35    let user_id = request
36        .headers()
37        .get("X-User-Id")
38        .and_then(|v| v.to_str().ok())
39        .and_then(|s| s.parse::<i64>().ok());
40
41    if let Some(user_id) = user_id {
42        let tenant_id = request
43            .headers()
44            .get("X-Tenant-Id")
45            .and_then(|v| v.to_str().ok())
46            .and_then(|s| s.parse::<i64>().ok());
47
48        let username = request
49            .headers()
50            .get("X-Username")
51            .and_then(|v| v.to_str().ok())
52            .unwrap_or("")
53            .to_string();
54
55        let ctx = RequestContext {
56            user_id,
57            tenant_id,
58            username,
59        };
60
61        request.extensions_mut().insert(ctx);
62    }
63
64    next.run(request).await
65}
66
67/// Axum Extractor:从请求扩展中提取 RequestContext(必须认证)
68///
69/// 使用方式:
70/// ```rust
71/// async fn handler(ctx: RequestContext) -> impl IntoResponse {
72///     format!("Hello, user {}", ctx.user_id)
73/// }
74/// ```
75///
76/// 如果请求未经网关认证(无 X-User-Id header),返回 401。
77impl<S> FromRequestParts<S> for RequestContext
78where
79    S: Send + Sync,
80{
81    type Rejection = Response;
82
83    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
84        parts
85            .extensions
86            .get::<RequestContext>()
87            .cloned()
88            .ok_or_else(|| {
89                (StatusCode::UNAUTHORIZED, "Missing user context").into_response()
90            })
91    }
92}
93
94// 可选的请求上下文(白名单接口使用)
95//
96// 使用方式:
97// ```rust
98// async fn handler(ctx: Option<RequestContext>) -> impl IntoResponse {
99//     match ctx {
100//         Some(c) => format!("Hello, user {}", c.user_id),
101//         None => "Hello, anonymous".to_string(),
102//     }
103// }
104// ```
105//
106// 注意:`Option<RequestContext>` 由 Axum 原生支持,
107// 当 `RequestContext` 提取失败时返回 `None`,无需额外实现。