Laravel RateLimiter的使用

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 do 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');
});

感谢各位的收看,我们下期再见.

上一篇:RateLimiter 平滑限流之非突发、非预热


下一篇:Java限流——RateLimiter使用