通过Laravel内置脚手架快速实现用户认证
系统自带脚手架
数据库迁移
新安装的laravel应用中都会包含下面两个迁移文件,分别用于创建用户表和密码重置表,这两张表在用户认证和找回密码中会用到:
User模型
laravel框架还在app
目录下为我们提供了与用户表相对相应的User
模型,在基于Eloquent模型驱动的认证提供者中,我们通过该模型实现登录认证,可以在配置文件config/auth.php
中查看相应的配置:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
如果不是通过User
模型类进行认证,可以在这里修改对应的model
配置项。如果不想通过Eloquent模型驱动,而是基于原声生数据库查询,可以注释掉eloquent
对应的users
配置,启用下面这个database
对应的users
配置,这样的话就直接去查询users
表,而不是通过模型类进行认证了。
User
模型类代码:
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
}
如果某个类需要用于认证,必须继承自use Illuminate\Foundation\Auth\User
基类,否则会报错。然后我们在这个模型中使用了Notifiable
Trait,里面提供了用户发送通知的相关方法。我们在白名单$fillable
中配置了三个字段,这三个字段会在登录和注册时用到,最后我们还配置了$hidden
属性,再返回查询结果的时候将敏感信息过滤掉,避免安全隐患。
认证中间件
laravel框架内置了几个认证中间件,用于在需要认证的路由中拒绝未认证用户发起的请求,或者将未登录的用户重定向到认证页面。打开app/Http/Kernel.php
,可以在$routeMiddleware
看到对应的路由中间件。
我们平时主要用到的是auth
中间件和guest
中间件,auth.basic
用户基于HTTP的简单认证,很少用到,throttle
中渐渐会在用户多次登录失败时使用,规定时间内登录失败超过指定次数不允许继续发起登录请求。提高系统安全性。
auth
中间件是\App\Http\Middleware\Authenticate::class
的别名,Authenticate
主要用于将未登录的用户重定向到登录页面:
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function redirectTo($request)
{
return route('login');
}
}
guest
中间件是 \App\Http\Middleware\RedirectIfAuthenticated::class
的别名,RedirectIfAuthenticated
主要用于将已登录用户重定向到认证后页面,未登录则继续原来的请求:
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/home');
}
return $next($request);
}
}
认证控制器
laravel还为我们提供了注册、登录、重置密码、邮箱验证、忘记密码对应的控制器:
其中 ForgotPasswordController
用于忘记密码后通过填写注册邮箱发送重置密码链接,对应逻辑位于 Illuminate\Foundation\Auth\SendsPasswordResetEmails
Trait 中。
LoginController
用于用户登录和退出,对应逻辑位于 Illuminate\Foundation\Auth\AuthenticatesUsers
Trait 中。
RegisterController
用于新用户注册,对应逻辑位于 Illuminate\Foundation\Auth\RegistersUsers
Trait 中。
ResetPasswordController
用于重置密码,对应逻辑位于 Illuminate\Foundation\Auth\ResetsPasswords
Trait 中。
上述控制器的构造函数中都应用了 guest
中间件(退出功能除外),表示这些控制器提供的方法都是给未登录用户使用的。
通过Artisan命令快速实现注册登录
php artisan make:auth # 注册认证路由、发布认证视图
php artisan migrate # 创建认证相关数据表,如果之前已经运行过,可以跳过
npm run dev # 编译前端资源,如果之前已经运行过,可以跳过
make:auth
命令会在 routes/web.php
中注册以下路由:
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
home
路由是用户认证成功后默认跳转路由,Auth::routes()
则包含以下路由定义:
// Authentication Routes...
$this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
$this->post('login', 'Auth\LoginController@login');
$this->post('logout', 'Auth\LoginController@logout')->name('logout');
$this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
$this->post('register', 'Auth\RegisterController@register');
// Password Reset Routes...
$this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
$this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
$this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
$this->post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update');
// Email Verification Routes...
$this->get('email/verify', 'Auth\VerificationController@show')->name('verification.notice');
$this->get('email/verify/{id}', 'Auth\VerificationController@verify')->name('verification.verify');
$this->get('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
此外,该命令还会在 resources/views
下发布以下登录认证相关的视图文件:
resources/views/home.blade.php
resources/views/layouts/app.blade.php
resources/views/auth/login.blade.php
resources/views/auth/register.blade.php
resources/views/auth/verify.blade.php
resources/views/auth/passwords/email.blade.php
resources/views/auth/passwords/reset.blade.php
这样,不需要编写任何代码,只需要简单运行几个命令,就可以在 Laravel 应用中实现登录认证功能了。这样,我们点击首页的右上角的登录按钮,就可以进入登录页面了:
获取登录用户信息
通过Auth门面
$user = Auth::user();//获取当前登录用户的完整信息
$userId = Auth::id();//获取当前登录用户ID
上述返回的$user
数据是一个User
模型实例,可以通过它获取用户的所有信息。此外还可以通过Auth
门面提供的其他方法快速进行一些常见判断,比如判断用户是否已经登录,可以通过Auth::check()
方法,如果已经登录返回true
,否则返回false
。相对的,还有Auth::guest()
方法判断用户是否未登录,逻辑与Auth::check()
方法刚好相反。
Blade指令
@auth
// 用户已登录...
@endauth
@guest
// 用户未登录...
@endguest
通过Request实例
public function update(Request $request)
{
$user = $request->user(); # 获取当前登录用户实例
}
登录失败次数限制
默认阈值是 1 分钟内尝试 5 次,超过这个次数就会提示失败次数过多,过段时间再来尝试。对于未登录用户而言,这个限制维度是基于 IP 的。对应的实现细节位于 Illuminate\Foundation\Auth\ThrottlesLogins
中。
如果你想要修改这个阈值,可以在 LoginController
控制器中通过设置如下属性来实现:
// 单位时间内最大登录尝试次数
protected $maxAttempts = 3;
// 单位时间值
protected $decayMinutes = 30;
支持用户名/邮箱登录
通过用户名登录
laravel支持在用户名和邮箱之间切换登录,默认是通过注册邮箱登录的,如果你想要通过用户名登录,很简单在LoginController
控制器中定义一个username()
方法重写AuthenticatesUser
Trait中的同名方法即可,Laravel底层通过该方法定义登录字段名。
public function username()
{
return 'name';
}
通过用户名或邮箱登录
有时候我们的登录字段可能需要同时支持用户名/注册邮箱/手机号登录,即用户既可以在登录框中输入邮箱,也可以输入用户名,这个时候,Laravel 框架底层自带的 UserProvider 已经满足不了这个需求了,我们需要扩展默认的 EloquentUserProvider
来实现这个功能。
实现此功能有个前提,那就是用户名、手机号和邮箱字段一样,在数据库中是唯一的
自定义 UserProvider
创建一个继承自底层 EloquentUserProvider
的子类,并将其存放到 app/Extensions
目录下,重写父类用户记录获取方法 retrieveByCredentials
如下:
<?php
namespace App\Extensions;
use Illuminate\Support\Str;
use Illuminate\Auth\EloquentUserProvider as BaseUserProvider;
class EloquentUserProvider extends BaseUserProvider
{
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials) ||
(count($credentials) === 1 &&
array_key_exists('password', $credentials))) {
return;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->createModel()->newQuery();
// 用于标识是否是 一个登录字段,如果包含多个登录字段,使用 OR 查询
$flag = false;
foreach ($credentials as $key => $value) {
if (Str::contains($key, 'password')) {
continue;
}
if ($flag) {
$query->orWhere($key, $value);
} else {
$query->where($key, $value);
$flag = true;
}
}
return $query->first();
}
}
到 app/Providers/AuthServiceProvider.php
的 boot
方法中使用自定义的 UserProvider 覆盖系统自带的 EloquentUserProvider
:
// 通过自定义的 EloquentUserProvider 覆盖系统默认的
Auth::provider('eloquent', function ($app, $config) {
return new EloquentUserProvider($app->make('hash'), $config['model']);
});
修改控制器传入字段
在LoginController.php
控制器中,设置一个新的属性用于包含系统支持的登录字段:
// 支持的登录字段
protected $supportFields = ['name', 'email'];
重写 AuthenticatesUsers
中的 credentials
方法用于传入系统支持的所有登录字段,并将其值都设置为用户在登录表单输入框中输入的值:
// 将支持的登录字段都传递到 UserProvider 进行查询
public function credentials(Request $request)
{
$credentials = $request->only($this->username(), 'password');
foreach ($this->supportFields as $field) {
if (empty($credentials[$field])) {
$credentials[$field] = $credentials[$this->username()];
}
}
return $credentials;
}