laravel通过ThrottlesLogins这个trait来实现登录验证的功能,我们只需要在控制器中use ThrottlesLogins这个trait便可以开启这个功能,注意:默认的make:auth的login控制器在trait AuthenticatesUsers中开启了该功能。
下面我们来分析登录限流是怎样实现的。
登录限流实际上是通过判断连续登录失败的次数是否超过我们限制的次数来实现的
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
hasTooManyLoginAttempts()判断当前的登录次数是否超过限制,如果超过限制,则触发一个锁定事件并通知其监听者,然后返回一个锁定的响应。
其中具体的逻辑如下,先来看看是如果判断登录次数是否超过限制的。
protected function hasTooManyLoginAttempts(Request $request)
{
//限制次数5,锁定时间1分钟
return $this->limiter()->tooManyAttempts(
$this->throttleKey($request), 5, 1
);
}
$this->limiter()用于获取一个Illuminate\Cache\RateLimiter实例,所以看出我们实际上是调用了底层的RateLimiter中的tooManyAttempts()方法。
public function tooManyAttempts($key, $maxAttempts, $decayMinutes = 1)
{
//has()方法检查是否存在对应lockout缓存
if ($this->cache->has($key.':lockout')) {
return true;
}
//如果没有的话,attempts($key)返回$key对应的缓存中存储的登录次数
if ($this->attempts($key) > $maxAttempts) {
//如果已经超出限制,调用add方法添加对应lockout到缓存
$this->cache->add($key.':lockout', Carbon::now()->getTimestamp() + ($decayMinutes * 60), $decayMinutes);
//重置当前登录次数
$this->resetAttempts($key);
return true;
}
return false;
}
$this->cache返回的是一个实现了Illuminate\Contracts\Cache\Repository接口的对象,相关的依赖由container注入,这里我们就不继续深入了,相关分析注释在代码中。
$this->cache->add($key.':lockout', Carbon::now()->getTimestamp() + ($decayMinutes * 60), $decayMinutes);
这里通过设置一个有效期为$decayMinutes的locktout缓存,传入的参数Carbon::now()->getTimestamp() + ($decayMinutes * 60)
设置了缓存的失效时间,也就是锁定的时间
如果经过判断,我们的登录次数已经超过的界定,那么,我们就会调用$this->fireLockoutEvent($request);
来通知该时间的监听者,具体的监听者我们可以在EventServiceProvider中的listeners数组中添加下面这项:
'Illuminate\Auth\Events\Lockout' =>['your listener', ]
通知后我们就需要对这个登录超限做出响应了.
protected function sendLockoutResponse(Request $request)
{
//RateLimiter的availableIn()返回锁定状态的剩余时间,通过比对之前设置的lockout缓存过期时间和当前时间
$seconds = $this->limiter()->availableIn(
$this->throttleKey($request)
);
//未知
$message = Lang::get('auth.throttle', ['seconds' => $seconds]);
//返回并将输入的表单信息选项保存到Session中以便在表单中保存之前输入的数据,同时提交剩余错误信息和锁定时间
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors([$this->username() => $message]);
}
如果我们并未触发登录的限制,那么才会验证登录信息,注意在login方法中我们一开始做的$this->validateLogin($request)
仅仅是验证输入信息是否合法而非验证登录的有效性。
//获取请求中需要的登录证书,就是返回用来登录的字段(这个可以在username()方法中设置)和密码
$credentials = $this->credentials($request);
//验证登录证书
if ($this->guard()->attempt($credentials, $request->has('remember'))) {
//返回登录成功的响应
return $this->sendLoginResponse($request);
}
//登录失败了,就会增加一次登录失败的次数
$this->incrementLoginAttempts($request);
//然后返回登录失败的响应
return $this->sendFailedLoginResponse($request);
如果登录成功了,那么给出的响应是:
protected function sendLoginResponse(Request $request)
{
//重新生产session id,防止Session fixation攻击
$request->session()->regenerate();
//清除之前登录次数的记录
$this->clearLoginAttempts($request);
//这里默认会返回我们设置的重定向路径,为了方便扩展??
return $this->authenticated($request, $this->guard()->user())?:redirect()->intended($this->redirectPath());
}
如果登录失败了,先增加登录失败次数的记录
incrementLoginAttempts($request)
实际调用了hit方法。
public function hit($key, $decayMinutes = 1)
{
//如果缓存不存在,则添加,并设置值为1
$this->cache->add($key, 1, $decayMinutes);
//存在,则增加
return (int) $this->cache->increment($key);
}
登录失败时的响应,同样和被锁定时一样跳转回登录界面并且携带信息,只是相比锁定时的响应少了锁定状态和剩余锁定时间的相关信息。
protected function sendFailedLoginResponse(Request $request)
{
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors([
$this->username() => Lang::get('auth.failed'),
]);
}
至此我们对laravel的登录功能应该已经有了较为完整的了解,也知道了登录限流的实现,最后来总结一下登录时候的流程:
另外,在这个过程中的两个功能函数列在下方,前者实现底层的缓存操作,后者实现底层的登录操作和验证操作,在这里我们就不深入分析了。
//登录失败次数缓存
$this->cache->add($key, 1, $decayMinutes);
//锁定状态缓存
$this->cache->add($key.':lockout', Carbon::now()->getTimestamp() + ($decayMinutes * 60), $decayMinutes);
对于记录登录失败次数的缓存,第一个参数是对应的key,第二个参数存放的值是登录失败的次数,第三个参数是有效期,通过hit($key, $decayMinutes = 1)
知道默认的有效期是1分钟,也就是限制了一分钟内登录次数最多为5,我们可以修改这个默认值来改变时间限制条件。
对应锁定状态缓存,除了第二个参数表示的是失效的时间点,其余和失败次数的缓存一样
$this->guard()->attempt($credentials, $request->has('remember')
$this->guard()返回IlluminateAuth\SessionGuard对象,然后调用其attempt方法实现登录操作,在这里就不深入分析了。