Skip to main content

sa_token_core/context/
mod.rs

1// Author: 金书记 | Author: Jin Shuji
2//
3//! Request Context | 请求上下文
4//!
5//! ## 存储策略 | Storage Strategy
6//!
7//! - **`tokio::task_local!`**(主路径 | primary path):跨 `await`、跨 Tokio worker 仍与同一异步任务绑定;
8//!   值为 `SaTokenContext`(内部 `Arc<RwLock<Inner>>`),**scope 内可突变**(`switch_to`)。
9//! - **`thread_local`**(兜底 | fallback):无 Tokio runtime 或纯同步测试路径;
10//!   行为与主路径 API 一致,但**不跨 spawn 继承**。
11//!
12//! ## 读取优先级 | Read Priority
13//!
14//! `try_current()`:**task-local 优先**,再回落 thread-local。
15//!
16//! ## 可变性设计(B3 核心修复)| Mutability Design (B3 core fix)
17//!
18//! **问题**:旧版 `SaTokenContext` 是 flat struct,`switch_to` 无法修改 `TASK_CTX.scope` 内的副本。
19//!
20//! **解决**:改为 `Arc<RwLock<Inner>>`,Clone 时共享句柄,`with_current_mut` 就地突变。
21//!
22//! ## 与 GrantScope 协调(B2 特性)| Coordination with GrantScope (B2 feature)
23//!
24//! `SaTokenContext::scope` 同时建立 token 上下文 + 授权快照(`TASK_GRANTS`),一次调用完成两者绑定。
25
26use std::cell::RefCell;
27use std::collections::HashMap;
28use std::future::Future;
29use std::sync::{Arc, RwLock};
30
31use crate::token::{TokenInfo, TokenValue};
32
33/// 上下文可变状态(内部数据,由 `Arc<RwLock>` 保护)
34///
35/// Mutable context state (internal data protected by `Arc<RwLock>`).
36///
37/// 字段 public:允许 `with_current_mut` 闭包内直接修改。
38/// Fields public: allows direct mutation inside `with_current_mut` closures.
39#[derive(Debug, Default)]
40pub struct SaTokenContextInner {
41    /// 当前请求的 token | Current request's token
42    pub token: Option<TokenValue>,
43
44    /// 当前请求的 token 信息 | Current request's token info
45    pub token_info: Option<Arc<TokenInfo>>,
46
47    /// 登录 ID(来自 token 解析)| Login ID (parsed from token)
48    pub login_id: Option<String>,
49
50    /// 身份临时切换目标 login_id(运行时动态设置,对应 `StpUtil::switch_to`)
51    ///
52    /// Temporary identity switch target (set dynamically via `StpUtil::switch_to`).
53    pub switch_login_id: Option<String>,
54
55    /// Headers needed by HTTP Basic / Same-Token macros.
56    /// HTTP Basic / Same-Token 宏所需的请求头快照。
57    pub auth_meta: RequestAuthMeta,
58}
59
60/// Headers needed by HTTP Basic / Same-Token macros (copied before `.await`).
61/// HTTP Basic / Same-Token 宏所需的请求头(在 `.await` 前拷贝)。
62#[derive(Debug, Clone, Default)]
63pub struct RequestAuthMeta {
64    /// Raw `Authorization` header (may be `Basic ...` or `Bearer ...`).
65    /// 原始 `Authorization` 头。
66    pub authorization: Option<String>,
67    /// Same-Token header value (header name from config).
68    /// Same-Token 头的值(头名来自配置)。
69    pub same_token: Option<String>,
70}
71
72impl RequestAuthMeta {
73    /// Capture from any `SaRequest`. Header lookup is adapter-defined.
74    /// 从任意 `SaRequest` 捕获。头查找语义由适配器决定。
75    pub fn from_request<R: sa_token_adapter::context::SaRequest>(
76        req: &R,
77        same_token_header: &str,
78    ) -> Self {
79        let authorization = req
80            .get_header("Authorization")
81            .or_else(|| req.get_header("authorization"));
82        let same_token = req.get_header(same_token_header).or_else(|| {
83            if same_token_header.eq_ignore_ascii_case("SA-SAME-TOKEN") {
84                req.get_header("sa-same-token")
85            } else {
86                None
87            }
88        });
89        Self {
90            authorization,
91            same_token,
92        }
93    }
94}
95
96thread_local! {
97    /// 同步兜底:仅在本 OS 线程可见,不跨 `tokio::spawn`
98    ///
99    /// Sync fallback: visible only within the current OS thread, does not cross `tokio::spawn`.
100    static TLS_CTX: RefCell<Option<SaTokenContext>> = const { RefCell::new(None) };
101
102    /// 请求级授权快照(thread-local 兜底,与 TASK_GRANTS 平行)
103    ///
104    /// Request-scoped authorization snapshot (thread-local fallback, parallel to TASK_GRANTS).
105    static TLS_GRANTS: RefCell<Option<GrantScope>> = const { RefCell::new(None) };
106}
107
108tokio::task_local! {
109    /// 异步主路径:与逻辑任务绑定,跨 await 有效
110    ///
111    /// Async primary path: bound to the logical task, survives across awaits.
112    static TASK_CTX: SaTokenContext;
113
114    /// 请求级授权快照(task-local 主路径,B2 特性)
115    ///
116    /// Request-scoped authorization snapshot (task-local primary, B2 feature).
117    static TASK_GRANTS: GrantScope;
118}
119
120/// 请求级授权快照(B2 特性,与 `SaTokenContext` 平行存储)
121///
122/// Request-scoped authorization snapshot (B2 feature, stored in parallel with `SaTokenContext`).
123///
124/// 内部用 `Arc<RwLock<HashMap>>`:[`SaTokenContext::current_grant_scope`] 返回的是
125/// **克隆**,但克隆共享同一份数据,因此在请求任意位置写入都能被后续读取看到。
126///
127/// Backed by `Arc<RwLock<HashMap>>`: `current_grant_scope` hands out clones that
128/// share one map, so a write anywhere in the request is visible to later reads.
129#[derive(Debug, Clone, Default)]
130pub struct GrantScope {
131    entries: Arc<RwLock<HashMap<String, Arc<[String]>>>>,
132}
133
134impl GrantScope {
135    /// 创建空快照 | Create an empty snapshot
136    pub fn new() -> Self {
137        Self::default()
138    }
139
140    /// 读取快照项 | Read an entry
141    pub fn get(&self, key: &str) -> Option<Arc<[String]>> {
142        let guard = self.entries.read().ok()?;
143        guard.get(key).map(Arc::clone)
144    }
145
146    /// 写入快照项 | Store an entry
147    pub fn put(&self, key: String, value: Arc<[String]>) {
148        if let Ok(mut guard) = self.entries.write() {
149            guard.insert(key, value);
150        }
151    }
152
153    /// 移除快照项(权限写操作后调用,使请求立即看到新值)
154    ///
155    /// Remove an entry after a write so the request sees the new value at once.
156    pub fn remove(&self, key: &str) {
157        if let Ok(mut guard) = self.entries.write() {
158            guard.remove(key);
159        }
160    }
161
162    /// 清空快照 | Clear the snapshot
163    pub fn clear(&self) {
164        if let Ok(mut guard) = self.entries.write() {
165            guard.clear();
166        }
167    }
168
169    /// 在给定 `Future` 期间挂载本快照(独立 API,兼容 B2)
170    ///
171    /// Mounts this snapshot for the duration of a future (standalone API, B2 compat).
172    ///
173    /// 优先使用 `SaTokenContext::scope`(正常 Web 请求同时需要 token + grant)。
174    /// Prefer `SaTokenContext::scope` for normal Web requests needing both token and grant.
175    pub async fn run<F, T>(scope: Self, future: F) -> T
176    where
177        F: std::future::Future<Output = T>,
178    {
179        TASK_GRANTS.scope(scope, future).await
180    }
181}
182
183/// 请求上下文句柄:`Clone` 共享同一 `Inner`,突变对所有克隆可见
184///
185/// Request context handle: `Clone` shares the same `Inner`; mutations are visible to all clones.
186///
187/// 字段私有(`inner: Arc<RwLock<...>>`):强制走 builder / accessor API。
188/// Fields private: forces use of builder/accessor API.
189#[derive(Clone)]
190pub struct SaTokenContext {
191    inner: Arc<RwLock<SaTokenContextInner>>,
192}
193
194impl std::fmt::Debug for SaTokenContext {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        match self.inner.read() {
197            Ok(guard) => f
198                .debug_struct("SaTokenContext")
199                .field("inner", &*guard)
200                .finish(),
201            Err(_) => f
202                .debug_struct("SaTokenContext")
203                .field("inner", &"<poisoned>")
204                .finish(),
205        }
206    }
207}
208
209impl SaTokenContext {
210    /// 创建空上下文 | Create an empty context
211    pub fn new() -> Self {
212        Self {
213            inner: Arc::new(RwLock::new(SaTokenContextInner::default())),
214        }
215    }
216
217    /// 链式 builder 入口(替代旧版 struct literal 字段赋值)
218    ///
219    /// Fluent builder entry (replaces old struct literal field assignment).
220    pub fn builder() -> SaTokenContextBuilder {
221        SaTokenContextBuilder::new()
222    }
223
224    // ==================== 字段 Accessor ====================
225
226    /// 读取 token(快照)| Read token (snapshot)
227    pub fn token(&self) -> Option<TokenValue> {
228        Self::read_inner(&self.inner).token.clone()
229    }
230
231    /// 读取 token_info(快照)| Read token_info (snapshot)
232    pub fn token_info(&self) -> Option<Arc<TokenInfo>> {
233        Self::read_inner(&self.inner).token_info.clone()
234    }
235
236    /// 读取 login_id(快照)| Read login_id (snapshot)
237    pub fn login_id(&self) -> Option<String> {
238        Self::read_inner(&self.inner).login_id.clone()
239    }
240
241    /// 读取 switch_login_id(快照)| Read switch_login_id (snapshot)
242    pub fn switch_login_id(&self) -> Option<String> {
243        Self::read_inner(&self.inner).switch_login_id.clone()
244    }
245
246    /// Snapshot of captured auth headers.
247    /// 已捕获鉴权头的快照。
248    pub fn auth_meta(&self) -> RequestAuthMeta {
249        Self::read_inner(&self.inner).auth_meta.clone()
250    }
251
252    // ==================== Scope 与 Task-Local 管理 ====================
253
254    /// 在 `fut` 全生命周期内绑定本上下文(跨 await / 跨 worker 仍有效),
255    /// **同时**建立一份请求级授权快照(B2 特性)。
256    ///
257    /// Binds this context for the whole lifetime of `fut` (survives across awaits/workers),
258    /// and simultaneously establishes a request-scoped authorization snapshot (B2 feature).
259    pub async fn scope<F, R>(ctx: SaTokenContext, fut: F) -> R
260    where
261        F: Future<Output = R>,
262    {
263        TASK_CTX
264            .scope(ctx, TASK_GRANTS.scope(GrantScope::new(), fut))
265            .await
266    }
267
268    /// 当前上下文副本:**task-local 优先**,再回落 thread-local
269    ///
270    /// Current context clone: **task-local first**, then fallback to thread-local.
271    pub fn try_current() -> Option<SaTokenContext> {
272        match TASK_CTX.try_with(|c| c.clone()) {
273            Ok(c) => Some(c),
274            Err(_) => TLS_CTX.with(|c| c.borrow().clone()),
275        }
276    }
277
278    /// 获取当前上下文(`try_current` 别名)
279    ///
280    /// Get current context (`try_current` alias).
281    pub fn get_current() -> Option<SaTokenContext> {
282        Self::try_current()
283    }
284
285    /// 设置当前上下文(thread-local 兜底路径,同时建立授权快照)
286    ///
287    /// Set current context (thread-local fallback path; also establishes authz snapshot).
288    ///
289    /// **scope 内**调用时会合并字段到现有句柄(不替换 Arc);
290    /// **scope 外**直接替换 thread-local 存储。
291    ///
292    /// Inside scope: merges fields into existing handle (does not replace Arc).
293    /// Outside scope: replaces thread-local storage.
294    pub fn set_current(ctx: SaTokenContext) {
295        if TASK_CTX.try_with(|_| ()).is_ok() {
296            let _ = Self::with_current_mut(|inner| {
297                let snap = Self::read_inner(&ctx.inner);
298                inner.token.clone_from(&snap.token);
299                inner.token_info.clone_from(&snap.token_info);
300                inner.login_id.clone_from(&snap.login_id);
301                inner.switch_login_id.clone_from(&snap.switch_login_id);
302                inner.auth_meta = snap.auth_meta.clone();
303            });
304            return;
305        }
306        TLS_CTX.with(|c| {
307            *c.borrow_mut() = Some(ctx);
308        });
309        TLS_GRANTS.with(|g| {
310            *g.borrow_mut() = Some(GrantScope::new());
311        });
312    }
313
314    /// 清除当前上下文与授权快照(thread-local 兜底;task-local 随 scope 结束自动 drop)
315    ///
316    /// Clear current context and authz snapshot (thread-local fallback; task-local auto-drops when scope ends).
317    pub fn clear() {
318        TLS_CTX.with(|c| {
319            *c.borrow_mut() = None;
320        });
321        TLS_GRANTS.with(|g| {
322            *g.borrow_mut() = None;
323        });
324    }
325
326    /// **单轨突变入口**:优先修改 task-local 共享 Inner,否则修改 thread-local(修复 B3-1/2)
327    ///
328    /// **Single-track mutation entry**: mutates task-local shared `Inner` first, else thread-local (fixes B3-1/2).
329    ///
330    /// **死锁警告**:禁止在闭包 `f` 内调用 `try_current()` 等读上下文方法!
331    ///
332    /// **Deadlock warning**: DO NOT call `try_current()` or other context-reading methods inside `f`!
333    pub fn with_current_mut<F, R>(f: F) -> Option<R>
334    where
335        F: FnOnce(&mut SaTokenContextInner) -> R,
336    {
337        if let Ok(handle) = TASK_CTX.try_with(|c| c.clone()) {
338            let mut guard = Self::write_inner(&handle.inner);
339            return Some(f(&mut guard));
340        }
341
342        TLS_CTX.with(|cell| {
343            let mut opt = cell.borrow_mut();
344            if opt.is_none() {
345                let auto_create = crate::util::StpUtil::try_get_config()
346                    .map(|c| c.context_auto_create)
347                    .unwrap_or(false);
348                if !auto_create {
349                    return None;
350                }
351                *opt = Some(SaTokenContext::new());
352            }
353            let handle = opt.as_ref()?;
354            let mut guard = Self::write_inner(&handle.inner);
355            Some(f(&mut guard))
356        })
357    }
358
359    /// 当前请求的授权快照;**优先 task-local**,再回落 thread-local(B2 特性)
360    ///
361    /// The current request's authorization snapshot; task-local first with a thread-local fallback (B2 feature).
362    pub fn current_grant_scope() -> Option<GrantScope> {
363        match TASK_GRANTS.try_with(|s| s.clone()) {
364            Ok(s) => Some(s),
365            Err(_) => TLS_GRANTS.with(|s| s.borrow().clone()),
366        }
367    }
368
369    /// 当前请求的账号体系(login_type);无上下文或字段为空时返回 `None`
370    ///
371    /// The current request's login type; `None` when absent or empty.
372    pub fn current_login_type() -> Option<String> {
373        Self::try_current()
374            .and_then(|ctx| ctx.token_info())
375            .map(|info| info.login_type.to_string())
376            .filter(|lt| !lt.is_empty())
377    }
378
379    // ==================== RwLock Poison Recovery ====================
380
381    /// 无 poison 语义的读锁(panic 后自动恢复内部数据)
382    ///
383    /// Poison-free read lock (recovers inner data after panic).
384    fn read_inner(
385        inner: &Arc<RwLock<SaTokenContextInner>>,
386    ) -> std::sync::RwLockReadGuard<'_, SaTokenContextInner> {
387        inner.read().unwrap_or_else(|e| e.into_inner())
388    }
389
390    /// 无 poison 语义的写锁
391    ///
392    /// Poison-free write lock.
393    fn write_inner(
394        inner: &Arc<RwLock<SaTokenContextInner>>,
395    ) -> std::sync::RwLockWriteGuard<'_, SaTokenContextInner> {
396        inner.write().unwrap_or_else(|e| e.into_inner())
397    }
398}
399
400impl Default for SaTokenContext {
401    fn default() -> Self {
402        Self::new()
403    }
404}
405
406/// Builder:供 router / 测试 / 插件构造上下文,避免公开 `inner` 字段
407///
408/// Builder: for router/tests/plugins to construct context without exposing `inner` field.
409pub struct SaTokenContextBuilder {
410    inner: SaTokenContextInner,
411}
412
413impl std::fmt::Debug for SaTokenContextBuilder {
414    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415        f.write_str("SaTokenContextBuilder { .. }")
416    }
417}
418
419impl SaTokenContextBuilder {
420    /// 创建空 builder | Create empty builder
421    pub fn new() -> Self {
422        Self {
423            inner: SaTokenContextInner::default(),
424        }
425    }
426
427    /// 设置 token | Set token
428    pub fn token(mut self, token: TokenValue) -> Self {
429        self.inner.token = Some(token);
430        self
431    }
432
433    /// 设置 token_info | Set token_info
434    pub fn token_info(mut self, info: Arc<TokenInfo>) -> Self {
435        self.inner.token_info = Some(info);
436        self
437    }
438
439    /// 设置 login_id | Set login_id
440    pub fn login_id(mut self, login_id: impl Into<String>) -> Self {
441        self.inner.login_id = Some(login_id.into());
442        self
443    }
444
445    /// 设置 switch_login_id(**仅测试用**,生产环境用 `StpUtil::switch_to` 运行时切换)
446    ///
447    /// Set switch_login_id (**test-only**; use `StpUtil::switch_to` for runtime switching in production).
448    pub fn switch_login_id(mut self, login_id: impl Into<String>) -> Self {
449        self.inner.switch_login_id = Some(login_id.into());
450        self
451    }
452
453    /// Set captured auth headers | 设置已捕获的鉴权头
454    pub fn auth_meta(mut self, meta: RequestAuthMeta) -> Self {
455        self.inner.auth_meta = meta;
456        self
457    }
458
459    /// 构建 `SaTokenContext`(消耗 builder)
460    ///
461    /// Build `SaTokenContext` (consumes builder).
462    pub fn build(self) -> SaTokenContext {
463        SaTokenContext {
464            inner: Arc::new(RwLock::new(self.inner)),
465        }
466    }
467}
468
469impl Default for SaTokenContextBuilder {
470    fn default() -> Self {
471        Self::new()
472    }
473}