使用Laravel实现用户身份认证

Laravel是一个“优雅的”PHP框架,其封装了一些常用功能,使得后端开发更加方便。这篇文章将从零介绍如何使用Laravel框架实现用户身份认证,保护网页中的受保护资源。

创建Laravel项目

推荐使用composer一键创建项目:

cd ~/www-test/
composer create-project laravel/laravel test5

尝试运行:

cd ~/www-test/test5
php artisan serve

默认在localhost的8000端口运行测试服务,浏览器打开,可以看到默认主页页面:

默认的主页页面

创建页面并设置路由

我们可以先创建几个简单的页面视图,并设置路由,查看效果。

创建页面,Laravel使用.blade.php的后缀命名视图文件:

创建一系列视图文件

我们使用authpage表示登陆页面,用来显示输入用户名和密码的表单;使用nonauth表示那些无需登陆即可查看的页面;使用secretpage表示需要认证才能查看的页面。

这些页面的内容可以临时随便填一下,主要是为了简单地弄懂认证状态下对各页面的路由。

编辑routes/web.php,按照逻辑将刚刚创建的几个页面添加进路由:

Route::get('/', function () {
    return view('welcome');
});

Route::get('/login', function () {
    return view('authpage.index');
})->name('login');

Route::get('/register', function () {
    return view('register.index');
})->name('register');

Route::get('/nonauth', function () {
    return view('nonauth.index');
});

Route::get('/secretpage', function () {
    return view('secretpage.index');
})->middleware('auth');

当路由后,使用view显示视图,Laravel框架就会从resources/views中查找相应的视图文件。若使用子文件夹,使用点表示路径分隔,如本例。

接下来再次运行服务,分别访问以上三个URL,查看效果:

登陆页面
非保护页面

同样地,访问/secretpage,发现被重定向到了登陆页面。

认证重定向-展开说明

在这里,我们仅仅定义了路由,添加了几个页面,怎么就实现了对未登陆用户的重定向了呢?其实这一切是由Laravel中间件实现的。

当访问/secretpage时,匹配到了我们编写的路由规则,因此Laravel知道,要将这个请求交给名为auth的中间件来处理一下。但是名为auth的中间件在哪里呢?参考官方手册-Middleware,发现定义于app/Http/Kernel.php:

    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,

于是,Laravel框架就知道了auth中间件定义的命名空间,我们顺着命名空间查看一下这个中间件的代码,位于app/Http/Middleware/Authenticate.php:

<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware; //2

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|null
     */
    protected function redirectTo($request)
    {
        if (! $request->expectsJson()) {
            return route('login'); //1
        }
    }
}

1处route(‘xxx’)返回某个命名路由的URL,通过调用这个方法,Laravel就知道要对未认证的用户重定向到哪里了。但此时你也许会纳闷,中间件的处理方法名称不应该是handle吗(参考官方文档-Middleware)?redirectTo方法应该不会被调用才对啊?

原来这里耍了个小花招,为了方便用户配置或定制,又避免在大量的代码中查找修改,2处使用了拓展类的方式来自定义中间件。查看真正的代码vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php:

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string[]  ...$guards
     * @return mixed
     *
     * @throws \Illuminate\Auth\AuthenticationException
     */
    public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($request, $guards);

        return $next($request);
    }

所以,这里才是真正的中间件处理入口。

到这里打住!我们仅仅设置了个路由,还远未实现完整的认证系统!关于认证中间件的不再展开了,有兴趣的同学也可以自己读一读源代码,感受一下Laravel的“优雅”。

配置数据库

于config/database.php配置你的数据库连接,可以参考官方文档-数据库配置

然后执行一下命令完成数据库迁移:

php artisan migrate
数据库迁移结果

接下来需要设置session存储方式。本文使用数据库存储session:

php artisan session:table
php artisan migrate

实现注册

要实现注册操作,我们需要实现两个功能:一个是用于用户注册的接口,一个是用户注册页面。用户使用浏览器打开register页面,页面上展现一个表单,用户填写表单后,将数据发往注册API接口。

注册API实现

为了实现更加复杂的操作,应该增加一个处理用户的控制器:

php artisan make:controller UserController

添加API路由routes/api.php:

use App\Http\Controllers\UserController;
...
Route::post('/register', [UserController::class, 'register']);

这样,请求api/register时,路由将调用UserController的register方法。下面完成这个方法的设计。

app/Http/Controllers/UserController.php文件内容如下:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
// use App\Http\Requests\RegisterPostRequest;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Hash;

use App\Models\User;

class UserController extends Controller
{
    public function register( Request $request ) {

        $validator = Validator::make($request->all(), [
            'name' => ['required', 'unique:users,name', 'max:30'],
            'email' => ['required', 'unique:users,email', 'email:rfc', 'max:255'],
            'password' => ['required', 'confirmed'],
            'password_confirmation' => ['required']
        ]);
        // var_dump($validator->validated());

        if ($validator->fails()) {
            return response()->json([
                'status' => 'failed',
                'message' => $validator->errors(),
            ]);
        } else {
            $validated = $validator->validated();
            $user = new User;
            $user->name = $validated['name'];
            $user->email = $validated['email'];
            $user->password = Hash::make($validated['password']);
            $user->save();

            return response()->json([
                'status' => 'success',
            ]);
        }

    }
}

思路大体上就是:首先使用Validator验证输入,若验证无误,使用User数据库模型存储数据,其中密码使用Hash加密。

使用Postman对注册接口进行请求,可以看到数据库添加了一行数据。

Postman请求接口
MySQL Workbench查看数据库

显然,最好通过邮件验证来防止恶意注册,且数据库也有这个邮箱激活时间的栏目。将在稍后实现这一功能。

注册页面实现

设计前端页面,使用表单或AJAX的方式向后端POST数据,即可完成注册,这里不再赘述,你也可以设计你自己的逻辑。

实现登陆

登陆接口实现

登陆实现更加简单。在UserController中编辑signin方法,就是照着官方文档这里写的,使用Auth Facade即可完成认证:

    public function signin( Request $request ) {

        $credentials = $request->validate([
            'email' => ['required', 'email:rfc', 'max:255'],
            'password' => ['required'],
        ]);

        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();
            return Redirect::to('/secretpage');
        } else {
            return back()->withErrors([
                'email' => 'The provided credentials do not match our records.',
            ])->onlyInput('email');
        }
    }

然后在web中添加路由。登陆最好不要使用api路由实现,因为Laravel默认不开启api的session中间件,换句话说,默认情况下只有web路由组才开启了session中间件,才能正常认证。

在routes/web.php中添加路由如下:

Route::get('/signin', [UserController::class, 'signin']);

登陆页面实现

    <div class="container">
        <br>
        <h1>登陆</h1>
        <hr>
        <ul>
            @foreach($errors->all() as $error)
            <li>{{ $error }}</li>
            @endforeach
        </ul>
        <form action="/signin" method="get">
            <div class="mb-3">
                <label for="email" class="form-label">Email</label>
                <input type="email" class="form-control" name="email">
            </div>
            <div class="mb-3">
                <label for="password" class="form-label">密码</label>
                <input type="password" class="form-control" name="password">
            </div>
            <button type="submit" class="btn btn-primary">登陆</button>
        </form>
    </div>

简单地使用表单实现就好。

登陆后即可正常访问受保护的页面secretpage。

实现注销

不得不说,有了Auth Facade,用户管理流程被大大简化了。在UserController中编辑signout方法即可:

    public function signout( Request $request ) {
        if (Auth::check()) {
            Auth::logout();
        }
        return Redirect::to('/login');
    }

然后添加路由:

Route::get('/signout', [UserController::class, 'signout']);

退出登陆后再次访问secretpage,发现被重定向到了登陆页面,注销成功。

实现邮箱验证

参考官方文档-邮箱验证,发现实际上Laravel也为我们提供了基本的邮箱验证功能。仅需配置一些路由、视图即可。

添加web路由:

// 提示需要进行邮箱认证
Route::get('/email/verify', function () {
    return view('auth.verify-email');
})->middleware('auth')->name('verification.notice');

// 认证链接
Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
    $request->fulfill();
    return redirect('/home');
})->middleware(['auth', 'signed'])->name('verification.verify');

// 重新发送认证邮件
Route::get('/email/verification-notificati  on', function (Request $request) {
    $request->user()->sendEmailVerificationNotification();
    return back()->with('message', 'Verification link sent!');
})->middleware(['auth', 'throttle:6,1'])->name('verification.send');

然后按照路由添加一些视图,确保这些视图打得开。这里就不再赘述添加视图的方法。

修改User数据库模型:

class User extends Authenticatable implements MustVerifyEmail

最后,在UserController添加一个消息,使之在用户添加后执行:

            $user->save();
            event(new Registered($user));

鉴于现在已经完成对用户邮箱进行验证的功能,我们可以仅允许已认证的用户访问受保护的资源:

Route::get('/secretpage', function () {
    return view('secretpage.index');
})->middleware('auth');

改为

Route::get('/secretpage', function () {
    return view('secretpage.index');
})->middleware('verified');

即可。

调试和查看

推荐使用Laravel Telescope来调试发送的邮件。安装流程可参考官方文档-Telescope

使用Telescope查看发送的邮件

由此,一个略为完整的身份认证页面就搭建好了,当然了,为了让新手更快上手,这篇文章的例子有点简单。你完全可以发挥自己的想象力,搭建一个自己的认证系统,并且设计一个用户中心。

发表评论