Skip to main content

sa_token_core/
http_basic.rs

1// Author: 金书记 | Author: Jin Shuji
2//
3//! HTTP Basic credential check against the current request's `Authorization` header.
4//! 基于当前请求 `Authorization` 头的 HTTP Basic 凭据校验。
5
6use crate::context::SaTokenContext;
7use crate::error::{SaTokenError, SaTokenResult};
8use crate::util::StpUtil;
9
10/// Default realm string used in `WWW-Authenticate`.
11/// `WWW-Authenticate` 使用的默认 realm。
12pub const DEFAULT_REALM: &str = "sa-token";
13
14/// Constant-time equality for equal-length byte slices.
15/// 等长字节的恒定时间比较(长度不同时直接 false,长度会泄漏,可接受)。
16pub fn ct_eq(a: &[u8], b: &[u8]) -> bool {
17    if a.len() != b.len() {
18        return false;
19    }
20    let mut diff = 0u8;
21    for (x, y) in a.iter().zip(b.iter()) {
22        diff |= x ^ y;
23    }
24    diff == 0
25}
26
27/// Decode `Authorization: Basic <base64(user:pass)>` into `user:pass`.
28/// 将 `Authorization: Basic <base64(user:pass)>` 解码为 `user:pass`。
29pub fn decode_basic_authorization(header: &str) -> Option<String> {
30    let rest = header
31        .strip_prefix("Basic ")
32        .or_else(|| header.strip_prefix("basic "))?;
33    let rest = rest.trim();
34    if rest.is_empty() {
35        return None;
36    }
37    use base64::Engine;
38    let bytes = base64::engine::general_purpose::STANDARD
39        .decode(rest.as_bytes())
40        .ok()?;
41    String::from_utf8(bytes).ok()
42}
43
44/// Check HTTP Basic.
45///
46/// `account` format: `user:password`. Empty `account` falls back to `config.http_basic`.
47/// `account` 格式为 `user:password`;空则回退 `config.http_basic`。
48pub fn check(realm: &str, account: &str) -> SaTokenResult<()> {
49    let expected = if account.is_empty() {
50        StpUtil::try_get_config()
51            .map(|c| c.http_basic.clone())
52            .unwrap_or_default()
53    } else {
54        account.to_string()
55    };
56    if expected.is_empty() {
57        return Err(SaTokenError::BasicAuthFailed {
58            realm: realm.to_string(),
59        });
60    }
61
62    let header = SaTokenContext::try_current()
63        .and_then(|ctx| ctx.auth_meta().authorization)
64        .ok_or_else(|| SaTokenError::BasicAuthFailed {
65            realm: realm.to_string(),
66        })?;
67
68    let decoded =
69        decode_basic_authorization(&header).ok_or_else(|| SaTokenError::BasicAuthFailed {
70            realm: realm.to_string(),
71        })?;
72
73    if !ct_eq(decoded.as_bytes(), expected.as_bytes()) {
74        return Err(SaTokenError::BasicAuthFailed {
75            realm: realm.to_string(),
76        });
77    }
78    Ok(())
79}
80
81/// Check using default realm and config / explicit account.
82/// 使用默认 realm,以及配置或显式账号进行校验。
83pub fn check_account(account: &str) -> SaTokenResult<()> {
84    check(DEFAULT_REALM, account)
85}