通过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 基类,否则会报错。然后我们在这个模型中使用了NotifiableTrait,里面提供了用户发送通知的相关方法。我们在白名单$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()方法重写AuthenticatesUserTrait中的同名方法即可,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.phpboot 方法中使用自定义的 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;
}
posted @ 2020-08-01 09:41  _大可乐  阅读(543)  评论(0编辑  收藏  举报