Skip to main content

Throttle

Struct Throttle 

Source
pub struct Throttle<S: ThrottleStore> { /* private fields */ }
Expand description

滑动窗口限流 / 封禁闸门。

§key 由调用方构造

key 是计数桶的名字,本模块只把它当作不透明字符串。约定形如 format!("ip:{}", ip)(按来源)或 format!("acct:{}", user)(按账户); 两类 key 前缀不同,天然互不干扰,可同时启用。

不要把用户输入直接当 key:拿请求里原样的用户名 / 任意 header 当 key, 攻击者只要每次换一个值就能把自己拆成无限多个桶,限流形同虚设; 空 key 同理,会让所有构造失败的请求共用同一个桶。调用方须先规范化 (截断长度、统一大小写、限制字符集),并保证 key 非空。

Implementations§

Source§

impl<S: ThrottleStore> Throttle<S>

Source

pub fn new(store: S, config: ThrottleConfig) -> Self

Source

pub fn config(&self) -> &ThrottleConfig

Source

pub fn check(&self, key: &str, now: u64) -> ThrottleDecision

请求进入时调用。先查封禁,再算剩余额度。

存储报错 → ThrottleDecision::Unavailable(见该变体的说明,不 fail-closed): 这里是限流的纵深防御,不是主认证闸门。后端故障时返回 Banned 会把全体 用户挡在门外(自我 DoS,且攻击者可能主动诱发),而放行只是暂时失去 暴力破解防护 —— 主认证闸门 SessionGuard 仍然在拦。调用方拿到 Unavailable 后自行选择,建议放行 + 告警。

Source

pub fn check_any(&self, keys: &[&str], now: u64) -> ThrottleDecision

同时检查多个维度(如 [ip_key, account_key]),返回最严格的结果。

合并规则(严格度):任一 Banned → Banned(取最晚的 until); 否则任一 Unavailable → Unavailable;否则 Allow 取最小 remaining。

每个 key 各自独立查询,不合并计数:ip: 与 acct: 是两类互不干扰的桶, 合并会让 NAT 后面的其他人替攻击者吃掉额度。

空 keys 什么都查不到,返回 Allow { remaining: 0 } 而非满额 —— 这个数字 会被写进 X-RateLimit-* 响应头,凭空报满额等于谎报额度。

Source

pub fn record_failure( &self, key: &str, now: u64, ) -> Result<ThrottleOutcome, StoreError>

认证失败时调用。达到 threshold 就封禁并返回 Banned,否则返回 Allow { remaining }。

返回 ThrottleOutcome 而非 ThrottleDecision:这里不存在 Unavailable —— 存储故障走 Err,两个可达状态对应两个分支,调用方不必再写一个永不执行的 第三个臂。判断「要不要拦这个请求」用 Throttle::check_any。

判定顺序:先拿窗口内计数,count >= threshold 时写封禁并返回解封时刻。 threshold 是「第几次失败触发封禁」,因此第 threshold 次调用返回的是 Banned 而不是 Allow { remaining: 0 } —— 剩余额度为 0 的那次已经是拒绝。

注意 record_failure(计数)与 ban(封禁)是两次独立的锁获取,不原子: 两者之间并发一次 reset 是可能的,结果是刚认证成功的用户又被封上 (可用性问题,不构成绕过 —— 计数也确实已经记下了)。若那个窗口不可接受, 得把「计数 + 判阈值 + 写封禁」并成一个 store 操作。

同理,ban 写失败时计数已经落库:本调用返回 Err,但下一步 check 会看到 Allow { remaining: 0 }(额度确实耗尽),由调用方据此拒绝。

Source

pub fn record_success(&self, key: &str) -> Result<(), StoreError>

认证成功时调用:只清失败计数,保留封禁。

「凭据正确 ⇒ 不是暴力破解 ⇒ 顺手解封」只对 acct: 桶成立。对 ip: 这类 共享桶,封禁是 NAT / 代理后面的所有人共用的 —— 换成 reset(清计数 + 清封禁) 意味着桶里任意另一个用户认证成功,就能替爆破者解除封禁并洗掉计数。

这是有意的安全取舍,不是漏写的细节:代价是账户桶下用户被爆破牵连时, 即使立刻输对密码也要等满 ban_secs(默认 900 秒)才恢复;换来的是共享桶的 封禁不会被他人一次成功认证解除。封禁不需要在这里额外清理,is_banned 的 now 过滤会让它自然到期;要人工提前解封用 Throttle::reset。

Source

pub fn reset(&self, key: &str) -> Result<(), StoreError>

人工解封 / 解限。

Source

pub fn purge_expired(&self, now: u64) -> Result<usize, StoreError>

Auto Trait Implementations§

§

impl<S> Freeze for Throttle<S>
where S: Freeze,

§

impl<S> RefUnwindSafe for Throttle<S>
where S: RefUnwindSafe,

§

impl<S> Send for Throttle<S>

§

impl<S> Sync for Throttle<S>

§

impl<S> Unpin for Throttle<S>
where S: Unpin,

§

impl<S> UnsafeUnpin for Throttle<S>
where S: UnsafeUnpin,

§

impl<S> UnwindSafe for Throttle<S>
where S: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.