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}