Skip to main content

sa_token_core/
same_token.rs

1// Author: 金书记 | Author: Jin Shuji
2//
3//! Same-Token: shared secret for intra-cluster / gateway-to-service calls.
4//! Same-Token:集群内 / 网关到服务 调用使用的共享口令。
5//!
6//! Current value plus one previous value (grace window) are both accepted.
7//! 当前值与上一次值(宽限期)均视为有效。
8
9use std::time::Duration;
10
11use crate::error::{SaTokenError, SaTokenResult};
12use crate::util::StpUtil;
13
14/// Default header name.
15/// 默认请求头名。
16pub const DEFAULT_HEADER: &str = "SA-SAME-TOKEN";
17
18fn ttl(timeout_secs: i64) -> Option<Duration> {
19    if timeout_secs > 0 {
20        Some(Duration::from_secs(timeout_secs as u64))
21    } else {
22        None
23    }
24}
25
26fn generate_token() -> SaTokenResult<String> {
27    crate::token::random_hex(32)
28}
29
30/// Read current Same-Token without creating one.
31/// 读取当前 Same-Token(不自动创建)。
32pub async fn get_token_nh() -> SaTokenResult<Option<String>> {
33    let manager = StpUtil::try_get_manager()?;
34    let key = manager.keys().same_token();
35    manager.dao().get_string(&key).await
36}
37
38/// Read past Same-Token (grace window).
39/// 读取宽限期内的上一次 Same-Token。
40pub async fn get_past_token_nh() -> SaTokenResult<Option<String>> {
41    let manager = StpUtil::try_get_manager()?;
42    let key = manager.keys().same_token_past();
43    manager.dao().get_string(&key).await
44}
45
46/// Get current token, creating one if missing.
47/// 获取当前 token,不存在则创建。
48pub async fn get_token() -> SaTokenResult<String> {
49    if let Some(existing) = get_token_nh().await?
50        && !existing.is_empty()
51    {
52        return Ok(existing);
53    }
54    refresh_token().await
55}
56
57/// Whether `token` equals current or past value (constant-time on equal length).
58/// `token` 是否等于当前或宽限值(等长时恒定时间比较)。
59pub async fn is_valid(token: &str) -> SaTokenResult<bool> {
60    if token.is_empty() {
61        return Ok(false);
62    }
63    let current = get_token_nh().await?;
64    if current
65        .as_deref()
66        .is_some_and(|c| crate::http_basic::ct_eq(c.as_bytes(), token.as_bytes()))
67    {
68        return Ok(true);
69    }
70    let past = get_past_token_nh().await?;
71    Ok(past
72        .as_deref()
73        .is_some_and(|p| crate::http_basic::ct_eq(p.as_bytes(), token.as_bytes())))
74}
75
76/// Refresh: move current → past, CAS-write a new current.
77/// 刷新:当前值写入 past,再用 CAS 写入新的当前值。
78pub async fn refresh_token() -> SaTokenResult<String> {
79    let manager = StpUtil::try_get_manager()?;
80    let timeout = manager.config.same_token_timeout;
81    let ttl_opt = ttl(timeout);
82    let dao = manager.dao();
83    let cur_key = manager.keys().same_token();
84    let past_key = manager.keys().same_token_past();
85
86    let current = dao.get_string(&cur_key).await?;
87    if let Some(ref cur) = current {
88        if !cur.is_empty() {
89            dao.set_string(&past_key, cur, ttl_opt).await?;
90        }
91    }
92
93    let next = generate_token()?;
94    let expected = current.as_deref().filter(|s| !s.is_empty());
95    let won = dao.cas(&cur_key, expected, &next, ttl_opt).await?;
96    if won {
97        return Ok(next);
98    }
99    // Another instance won; return whatever is stored so callers do not split the cluster secret.
100    // 另一实例已写入;返回存储中的值,避免集群共享口令分叉。
101    dao.get_string(&cur_key)
102        .await?
103        .filter(|s| !s.is_empty())
104        .ok_or(SaTokenError::SameTokenInvalid)
105}
106
107/// Check a raw token string.
108/// 校验原始 token 字符串。
109pub async fn check_token(token: &str) -> SaTokenResult<()> {
110    if is_valid(token).await? {
111        Ok(())
112    } else {
113        Err(SaTokenError::SameTokenInvalid)
114    }
115}
116
117/// Check the Same-Token captured on the current request.
118/// 校验当前请求上下文中捕获的 Same-Token。
119pub async fn check_current_request() -> SaTokenResult<()> {
120    let value = crate::context::SaTokenContext::try_current()
121        .and_then(|ctx| ctx.auth_meta().same_token)
122        .unwrap_or_default();
123    check_token(&value).await
124}