Laravel RateLimiter的使用
上文說道laravel auth腳手架自帶的登陸方法中,存在嘗試次數限制,今天來補上
# trait AuthenticatesUsers
public function login(Request $request)
{
if (method_exists($this, 'hasTooManyLoginAttempts') &&
$this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
// 登陸失敗就增加次數
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
# trait ThrottlesLogins
/**
* Determine if the user has too many failed login attempts.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function hasTooManyLoginAttempts(Request $request)
{
# $this->limiter()返回laravel默認的RateLimter
# 以下三個方法可以方便地在LoginController中重寫
# $this->thorrleKey
# $this->maxAttempts
# $this->decayMinute
return $this->limiter()->tooManyAttempts(
$this->throttleKey($request), $this->maxAttempts()
);
}
# Illuminate\Cache\RateLimiter
# 注釋非常明確 各位自行查看就好 這里只追部分代碼
# 可以看到這個類通過緩存存放了key 和 key:timer
# 當我們從容器中解析RateLimter時,還可以選擇對應的cache driver
# app(RateLimiter::class, ['cache' => Cache::store('redis')])
# 你也可以使用laravel容器提供的when needs give等api,在服務提供者中自定義你的ratelimiter
# 希望能夠帶給你些許啟發
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
/**
* Determine if the given key has been "accessed" too many times.
*
* @param string $key
* @param int $maxAttempts
* @return bool
*/
# 我們可以看到這個限制器是根據key指定的 那么就意味着我們可以隨意將他用到自己的代碼中了啊
# 有點感覺了嗎?
public function tooManyAttempts($key, $maxAttempts)
{
if ($this->attempts($key) >= $maxAttempts) {
if ($this->cache->has($key.':timer')) {
return true;
}
$this->resetAttempts($key);
}
return false;
}
# 如果你真的看了RateLimter的代碼,那么應該會猜到登陸失敗的話,就會調用limter的hit方法
幾種可能會對你有幫助的用法
方式一 保護某個控制器或者方法等
1 創建一個中間件
2 編寫中間件,這個中間件你可能希望他長成這樣,這里只是簡單的例子,你可以根據文檔創建功能更強大的中間件
public function __construct(RateLimiter $rateLimiter)
{
$this->limiter = $rateLimiter
}
public function handle($request, Closure $next)
{
$accessKey = $request->route()->getActionName();
// or any key you like
if ($this->limiter->tooManyAttempts($accessKey, 10)) {
return back()->withErrors('kind of hurt, buddy!');
// you can also throw some custom exceptions, feel free to express yourself
}
$this->limiter->hit($accessKey);
return $next($request);
}
3 注冊該中間件
4 使用中間件
# 這里只是將限制器的代碼方法放到中間件了,你當然可以將類似的邏輯放到你的代碼的任何地方
# 你也可以使用帶參數的中間價用來限定某個控制器中的方法的訪問頻率等等
# 這里的key 嘗試次數 窗口時間等 都可以通過中間件參數傳遞進來
# it's your zone, do whatever you like
# 稍微完整些的示例
# 中間件代碼
protected $limiter;
public function __construct(RateLImiter $limiter)
{
$this->limiter = $limiter;
}
public function handle(Request $request, Closure $next, $key, $attempts)
{
if ($this->limiter->tooManyAttempts($key, $attempts)) {
dd('got throttled!');
}
$this->limiter->hit($key);
return $next($request);
}
# 控制器代碼
public function __construct(Request $request)
{
$action = $request->route()->getActionName();
$this->middleware("limiter:{$action},2");
}
public function index()
{
dump('limiter test');
}
# 這樣當你第三次訪問的時候 默認的就會被禁止訪問一分鍾了
# 當然也可以將類似的邏輯移動到某個trait或者助手函數中,但這可能會侵入業務代碼
# 類似於裝飾器的模式 給真正要執行的業務代碼再加上一層 各位自行嘗試吧
方式二 laravel自帶的throttle中間件
# Illuminate\Routing\Middleware\ThrottleRequests
public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
{
$key = $this->resolveRequestSignature($request);
$maxAttempts = $this->resolveMaxAttempts($request, $maxAttempts);
if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
throw $this->buildException($key, $maxAttempts);
}
$this->limiter->hit($key, $decayMinutes * 60);
$response = $next($request);
return $this->addHeaders(
$response, $maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts)
);
}
# 相信有了上面的感覺,這段代碼看到這就能理解這個中間件是干嘛的了
方式三 laravel8中的RateLimiter有了新的功能,各位可以嘗試使用新的方式實現
# 以下代碼抄自laravel官方
// Standard rate limit for the entire application users (sanity check...)
RateLimiter::for('global', function (Request $request) {
return Limit::perMinute(1000);
});
// Limiting based on a custom key segment... such as IP address or anything else you want...
RateLimiter::for('uploads', function (Request $request) {
Limit::perMinute(10)->by($request->ip()),
});
// Returning no rate limit for certain customers...
RateLimiter::for('podcasts', function (Request $request) {
if ($request->user()->vipCustomer()) {
return Limit::none();
}
Limit::perMinute(5)->by($request->ip()),
});
// Returning an array of rate limits the request must pass through...
RateLimiter::for('logins', function (Request $request) {
return [
Limit::perMinute(100),
Limit::perMinute(3)->by($request->input('email')),
];
});
Route::middleware(['throttle:global'])->group(function () {
Route::get('/', function () {
return view('welcome');
});
Route::get('/upload', function () {
return view('welcome');
})->middleware('throttle:uploads');
});
感謝各位的收看,我們下期再見.
