通过 PHP 原生代码基于 Cookie + Session 机制实现后台用户认证功能

基于 Session 实现用户登录功能我们在前面的基础教程中已经演示过了,这里只需在其基础上进行改造即可。

准备工作

开始之前,我们先在控制器基类 App\Http\Controller\Controller 中新增一个 $session 变量作为 Session 实例,并在控制器中初始化:

class Controller
{
    ...

    /**
     * @var Session
     */
    protected $session;

    public function __construct()
    {
        ...
        $this->session = $this->container->resolve('session');
    }
}

另外,还需要在 app/config/app.php 中设置 Session 的有效期为 2 个小时:

'session' => [
    'lifetime' => 2 * 60 * 60
]

然后在 app 目录下新建一个 helper.php 用于存放辅助函数,这里我们定义一个 redirect 函数进行重定向操作:

<?php

if (!function_exists('redirect')) {
    function redirect($route, $statusCode = 301)
    {
        $response = new \App\Http\Response('', $statusCode, ['Location' => $route]);
        $response->send();
        exit();
    }
}

composer.json 中添加如下代码从而可以自动加载这个 helper.php 文件:

"autoload": {
    "files": [
        "app/helper.php"
    ],
    ...
}

这样,我们在控制器发送重定向响应时就无需编写一堆重复的代码了。

路由和控制器

注册路由

做好以上准备工作后,在 app/routes/web.php 中注册用户登录和退出路由:

$router->register(['get', 'post'], 'login', 'AuthController@login');
$router->register('post', 'logout', 'AuthController@logout');

注:由于后台仅限管理员登录,所以不提供用户注册功能。

用户登录处理

然后创建对应的 AuthController 控制器(位于 app/http/controller 目录下),先编写用户登录相关处理逻辑:

public function login()
{
    if ($this->session->has('auth_user')) {
        // 用户已登录,跳转到管理后台
        return redirect('/admin');
    }
    $siteName = $this->container->resolve('app.name');
    $pageTitle = '登录页面 - ' . $siteName;
    if ($this->request->getMethod() == 'GET') {
        $this->view->render('admin/login.php', compact('siteName', 'pageTitle'));
    } else {
        $name = $this->request->get('name');
        $password = $this->request->get('password');
        if (empty($name) || empty($password)) {
            $error = '用户名和密码不能为空';
            $this->view->render('admin/login.php', compact('siteName', 'pageTitle', 'error'));
            return;
        }
        $user = User::where('name', $name)->first();
        if (empty($user)) {
            // 返回到用户登录页面,并提示错误信息
            $error = '对应用户不存在,请重试';
            $this->view->render('admin/login.php', compact('siteName', 'pageTitle', 'error'));
            return;
        }
        if ($user->password == md5($password)) {
            // 用户登录成功
            $this->session->set('auth_user', $user);
            // 跳转到管理后台
            return redirect('/admin');
        }
        // 返回到用户登录页面,并提示错误信息
        $error = '用户名和密码不匹配,请重试';
        $this->view->render('admin/login.php', compact('siteName', 'pageTitle', 'error', 'name'));
        return;
    }
}

对于 GET /login 请求,会渲染用户登录页面。

对于 POST /login 请求,会处理用户输入的登录信息,如果用户名和密码与数据库中的对应记录匹配成功,则用户认证成功,并将用户信息存储到 Session,然后跳转到后台首页;否则将错误提示信息反馈到用户登录页面。

用户退出处理

至于用户退出逻辑,则简单许多:

从 Session 中移除认证用户信息,并跳转到登录页面。

视图模板

后台视图模板重构

开始编写用户登录视图模板之前,我们先对后台视图模板进行重构,因为对于后台视图而言,整体布局是一致的,头部、底部、导航、边栏代码都是可以复用的,没必要每个视图模板都重新编写一遍。

我们对之前的后台首页视图模板 resources/views/admin/index.php 按照组件进行拆分。

头部组件

resources/views/admin/header.php

源码:https://github.com/nonfu/master-laravel-code/blob/v1.1/practice/blog/resources/views/admin/header.php

导航组件

resources/views/admin/nav.php

源码:https://github.com/nonfu/master-laravel-code/blob/v1.1/practice/blog/resources/views/admin/nav.php

侧边栏组件

resources/views/admin/sidebar.php

源码:https://github.com/nonfu/master-laravel-code/blob/v1.1/practice/blog/resources/views/admin/sidebar.php

底部组件

resources/views/admin/footer.php

源码:https://github.com/nonfu/master-laravel-code/blob/v1.1/practice/blog/resources/views/admin/footer.php

主体代码

resources/views/admin/index.php

源码:https://github.com/nonfu/master-laravel-code/blob/v1.1/practice/blog/resources/views/admin/index.php

目前,上述视图模板中还存在很多硬编码,我们将在下篇教程中逐一将其替换成从后端读取变量进行渲染。

用户登录视图

完成上述视图模板重构后,编写用户登录页面就可以复用头部和底部组件了:

<?php include 'header.php';?>

<body class="bg-gradient-primary">

<div class="container">

    <!-- Outer Row -->
    <div class="row justify-content-center">

        <div class="col-xl-10 col-lg-12 col-md-9">

            <div class="card o-hidden border-0 shadow-lg my-5">
                <div class="card-body p-0">
                    <!-- Nested Row within Card Body -->
                    <div class="row">
                        <div class="col-lg-6 d-none d-lg-block bg-login-image"></div>
                        <div class="col-lg-6">
                            <div class="p-5">
                                <div class="text-center">
                                    <h1 class="h4 text-gray-900 mb-4">学院君管理后台</h1>
                                </div>
                                <form class="user" action="/login" method="POST">
                                    <div class="form-group">
                                        <input type="text" class="form-control form-control-user" id="name" name="name" aria-describedby="emailHelp" placeholder="用户名" value="<?php echo empty($name) ? '' : $name;?>">
                                    </div>
                                    <div class="form-group">
                                        <input type="password" class="form-control form-control-user" id="password" name="password" placeholder="密码">
                                    </div>
                                    <?php if (!empty($error)):?>
                                    <div class="alert alert-danger" role="alert">
                                        <?=$error?>
                                    </div>
                                    <?php endif;?>
                                    <button type="submit" class="btn btn-primary btn-user btn-block">
                                        登录
                                    </button>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

        </div>

    </div>

</div>

<?php include 'footer.php';?>

用户退出视图

用户退出通过一个模态框的交互来完成,对应的引用代码在导航组件 nav.php 中:

<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown">
    <a class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal">
        <i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
        Logout
    </a>
</div>

对应的模态框代码如下:

<!-- Logout Modal-->
<div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-hidden="true">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="exampleModalLabel">确定要退出?</h5>
                <button class="close" type="button" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">×</span>
                </button>
            </div>
            <div class="modal-body">点击下面的 "退出" 按钮退出管理后台</div>
            <div class="modal-footer">
                <button class="btn btn-secondary" type="button" data-dismiss="modal">取消</button>
                <form action="/logout" method="post">
                    <button class="btn btn-primary" type="submit" id="logoutBtn">退出</button>
                </form>
            </div>
        </div>
    </div>
</div>

测试用户认证功能

我们在 users 表中插入一条记录,然后在 DashboardController 控制器的构造函数中新增如下代码:

public function __construct()
{
    parent::__construct();
    if (!$this->session->has('auth_user')) {
        redirect('/login');
    }
}

表示如果用户没有登录的情况下访问博客后台,会重定向到登录页面。另外,在 DashboardControllerindex 方法中引入认证用户变量(用户认证后才能访问到这里),传递给视图模板进行渲染:

public function index()
{
    ...
    $user = $this->session->get('auth_user');
    $this->view->render('admin/index.php', compact('pageTitle', 'siteName', 'user'));
}

blog 根目录下运行 composer dump-auto 让上述代码修改导致的命名空间和自动加载调整生效:

-w689

我们在 public 目录下运行 php -S localhost:9000 启动这个博客项目,然后在浏览器中访问后台首页,由于用户尚未认证,所以会跳转到登录页面:

-w960

如果输入的用户名和密码不匹配,会提示错误信息:

-w939

登录凭证通过验证后,就可以登录成功,进入博客后台页面:

-w1431

点击右上角的用户头像,下拉框会出现退出按钮:

-w704

点击退出按钮,会弹出模态框进行退出确认:

-w498

确认退出后,页面会再次重定向到登录页面,表示用户退出成功。

关于用户认证的部分,学院君就简单介绍到这里,下篇教程,我们来完善后台专辑、文章、消息的增删改查功能,从而构建博客系统前后端功能闭环。

本篇教程源码已提交到 Github 仓库:https://github.com/nonfu/master-laravel-code/tree/v1.1/practice/blog

上一篇: 引入 SB Admin 2 作为后台管理系统主题

下一篇: 在博客后台为专辑、文章、消息模块实现增删改查功能