首页 > 解决方案 > Laravel 5:如何重置内置油门/速率限制器?

问题描述

我像这样使用 Laravel 的内置油门:

//File: Kernal
protected $middlewareGroups = [
'api' => ['throttle:10,3']
];

但是,我想在我的一个控制器中执行某些操作后重置计数(例如成功登录后)。

我可以看到这个中间件使用RateLimiter了一个名为clear.

问题是,这个怎么用?因为它依赖于keyfromThrottleRequests中间件。

  1. 要获得objectThrottleRequests需要的实例RateLimiter
  2. 要获得 的对象RateLimiter,我需要 的实例Cache。. .

总而言之,如何使用它没有尽头..有什么想法吗?

谢谢

标签: phplaravellaravel-5.5rate-limitingthrottling

解决方案


由于您的问题使用 Laravel v5.5 标记,因此适用于此处:

对于登录尝试:

您可以在控制器中使用该Illuminate\Foundation\Auth\AuthenticatesUsers特征,因此您可以访问该方法,该方法使用正确的密钥clearLoginAttempts调用实例上的方法,而无需提供密钥。clear()RateLimiter

实际上,如果您查看Illuminate\Foundation\Auth\ThrottlesLogins::clearLoginAttempts()其实现方式,您会发现$this->throttleKey($request),一旦您的控制器使用该AuthenticatesUsers特征,就可以通过 检索正确的键。

一般来说:

您始终可以Illuminate\Cache\RateLimiter使用 获取实例app(\Illuminate\Cache\RateLimiter::class),该实例又包含所有配置的限制器和缓存。问题是从这个角度来看是无法获取缓存键的。因此,您确实必须首先找出密钥的设置位置和方式,以便您可以使用相同的密钥进行重置。

标准ThrottleRequests中间件在handle()方法中设置密钥,但实际密钥将取决于配置限制的位置和方式(例如:它是命名限制器还是仅使用数字参数设置,被->by(...)调用以显式设置密钥等.)

如果您只需要找到一个特定限制器的密钥,则可能您可以在handle()方法中设置一个断点并进行检查。

你的情况

在您的特定情况下,由于它不是命名限制器,因此该handle()方法将调用resolveRequestSignature以获取密钥。我认为您不能轻松地从控制器访问中间件实例。您可以做的是检查该方法如何生成密钥并基本上复制那段代码以复制相同的密钥,但我不建议这样做,因为它是一个肮脏和脆弱的解决方案。如果您检查,您会看到密钥可以复制为:

if ($user = $request->user()) {
    $key = sha1($user->getAuthIdentifier());
}
elseif ($route = $request->route()) {
    $key = sha1($route->getDomain().'|'.$request->ip());
}

但是在最近的 Laravel 版本中,您可以显式设置密钥,这是更清洁和可靠的解决方案:


在 Laravel 8 中

现在由于这个问题已经相当老了,大多数人宁愿使用最新版本的 Laravel(截至 2021 年 2 月 12 日的 v8),所以对他们来说,文档包括“分段”限制器的方法。能够根据请求(或会话数据等)为不同的请求应用单独的限制计数器。实际上该by()方法实际上设置key了限制器。因此,您可以设置一个或多个命名限制器,例如:

RateLimiter::for('my_per_ip_limiter', function (Request $request) {
    return Limit::perMinute(100)->by($request->ip());
});

这意味着命名的限制器my_per_ip_limiter将使用 IP 作为密钥,因此您可以随时在控制器中调用:

app(\Illuminate\Cache\RateLimiter::class)->clear($request->ip());

重置特定 IP 的限制器。或获取到目前为止的尝试次数:

$attempts_so_far = app(\Illuminate\Cache\RateLimiter::class)->attempts($request->ip());

实际上,您可以使用请求(或会话或其他)的任何变量来代替 IP。

然而,没有办法(我认为)区分命名的限制器。因此,如果同一键也用于另一个限制器,则它们的命中将一起计算*并一起清除。因此,给my_per_ip_limiter限制器起一个名称只是有用的,因此您可以通过名称将该限制器分配给特定路由,例如:

Route::post( 'login', 'Auth\LoginController@login' )
       ->middleware('throttle:my_per_ip_limiter');

但是如果你真的需要命名限制器来单独重置,你必须使用一个唯一的键,例如在它前面加上一些东西,例如:

RateLimiter::for('my_other_ip_limiter', function (Request $request) {
    return Limit::perMinute(100)->by('other_'.$request->ip());
});

这可以独立于另一个清除:

// reset my_other_ip_limiter, but not my_per_ip_limiter :
app(\Illuminate\Cache\RateLimiter::class)->clear('other_'.$request->ip()); 

*:通过一起计数,我的意思是它们会相加,所以如果你将它们中的两个应用于同一个请求,每个请求都会使计数器增加 2!


推荐阅读