逐步创建一个 PHP 和 MariaDB 认证系统

使用 PHP 和 MariaDB 创建认证系统

使用框架来处理 PHP 应用程序的认证不仅完全可行,而且通常是被鼓励的做法。它们能将管理用户和会话的复杂操作抽象化,而这些操作对应用程序的正常运行至关重要。

不过,退一步去了解 PHP 中的认证原理也很重要,这会让你更精准地调试常用框架认证系统里的问题。

利用 PHP 创建认证系统其实相当简单,因为 PHP 内置了许多相关功能。实际上,框架只是把这些功能封装成一个连贯的认证模型,并且可以进行扩展以引入新特性。

在本文中,我将逐步介绍使用 PHP 和 MariaDB 创建认证系统的具体步骤。我假定你对 PHP 有一定了解,只是想深入探究如何对用户进行认证。此外,在这个过程中也能借鉴 Drupal 开发、Drupal 模块开发的一些思路,特别是在考虑系统的扩展性和安全性方面,与 Drupal 开发的理念有相似之处。倘若后续要对系统进行升级或优化,也能参考 Drupal 升级的相关经验,像 Drupal 11 在性能和安全性上的改进,都能为我们的系统带来启发。

各章节链接:

  • 搭建 DDEV
  • 项目结构
  • 连接到 MariaDB
  • 创建用户表
  • 使用 Bootstrap 进行主题设计
  • 创建登录表单
  • 用户认证
  • 保护页面
  • 欢迎用户
  • 注销登录
  • 运行完整项目
  • 总结

一、搭建 DDEV

为了简化这个项目的搭建流程,我们将采用 DDEV。它是一个基于 Docker 的环境,涵盖了 PHP 网站正常运行所需的各类元素。DDEV 具备多种配置方式,在这个项目里,我们会使用以下组件。

  • PHP 8.1。
  • 选用 Nginx 作为 Web 服务器。
  • 选用 MariaDB 10.4 作为数据库服务器。

MariaDB 在功能上和 MySQL 基本一致,用于和它交互的 PHP 代码被称作 MySQLi。你能够轻松地把它替换成 MySQL,而且代码的运行方式不会改变。

使用 DDEV 意味着我们无需在本地机器上安装这些程序,这有诸多好处,比如拥有一个稳定的开发平台,并且可以在不安装新软件的情况下重新配置。这个平台能加快并简化环境的安装和开发过程。

按照 DDEV 的安装说明操作,接着在新创建的目录中运行以下命令,开启项目的配置工作。

  
    ddev config
  

这会引导你回答几个问题来完成设置。首先是项目名称(也就是你当前所在的目录名称)。

  
    在当前目录中创建一个新的 DDEV 项目配置
    完成后,你的配置将被写入 .ddev/config.yaml

    项目名称 (当前目录名):
  

要是这个名称没问题,直接按回车键进入下一个问题。要记住,这将构成你本地开发环境的 URL。

接下来,DDEV 会询问“文档根目录”的位置。这是 Web 服务器的根目录,也就是你的 PHP 文件将被提供服务的目录。

  
    文档根目录是你的网站提供服务的目录。
    这是一个相对于项目根目录的路径
    如果你的网站文件位于项目根目录中,你可以留空此值
    文档根目录位置 (当前目录): web

    警告:提供的文档根目录 web 目前不存在。
    是否在 web 创建文档根目录? [Y/n] (yes): yes
  

出于本项目的需求,我们要把它设置为“web”。由于该目录不存在,DDEV 会询问是否创建,我选择了“是”。

最后,我们需要选择要处理的项目类型。这里有很多选项,但我们只需标准的“php”项目,所以输入“php”并按回车键。

  
    项目类型 [backdrop, craftcms, django4, drupal10, drupal6, drupal7,
    drupal8, drupal9, laravel, magento, magento2, php, python, shopware6,
    silverstripe, typo3, wordpress] (php): php
  

选择这个选项将完成配置步骤并设置好你的项目。

现在你可以访问相应网页,目前该页面没内容,所以你会看到一个“403 禁止访问”页面,因为 Web 服务器不知道如何处理该请求。

二、项目结构

在开始编写代码之前,我先展示一下我们最终会形成的项目结构。

  
    /web
      index.php
      login.php
      logout.php
      page.php
    database_connection.php
    install.php
  

要注意,/web 目录是 Web 服务器提供文件服务的目录。这意味着 database_connection.php 和 install.php 文件不在 Web 服务器的可见范围内。

虽说这不是必需操作,但通常把一些更机密的文件(像数据库连接详细信息)移出 Web 目录是个不错的办法,这样它们就不会意外地提供给用户。要是你以某种方式错误配置了 Web 服务器,可能会直接提供 PHP 源代码而不是解释执行它。如果发生这种情况,你只会泄露非敏感的 PHP 源代码,而不是数据库凭据。当然,PHP 也有可能崩溃并导致这种情况发生,不过这种可能性要小得多。

在开始之前,你可以随意创建这些文件。即便它们是空的,也能帮助你找到放置内容的正确位置。

三、连接到 MariaDB

有几种方式可以将 PHP 连接到 MariaDB 数据库,出于本项目的考虑,我们将使用改进版的 MySQL 库,也就是 mysqli 库。该库取代了在 PHP 7.0.0 中被移除的旧 MySQL 库,并且提供了一些对象或函数,让 PHP 能够连接到 MySQL 或 MariaDB 数据库。

为了处理数据库连接,我们会创建一个名为 database_connection.php 的文件。该文件将包含数据库参数并负责连接到数据库本身。

该文件只需包含以下代码。

  
    <?php

    // 数据库参数。
    $databaseHost = "db";
    $databaseUsername = "db";
    $databasePassword = "db";
    $databaseName = "db";

    // 确保 MySQL 客户端抛出适当的异常。这是 PHP 8.1 的默认设置。
    mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

    // 连接到数据库。
    try {
      $mysqli = new mysqli($databaseHost, $databaseUsername, $databasePassword, $databaseName);
    } catch (mysqli_sql_exception $e) {
      // 尝试连接数据库时发生错误。
      print('MySQLi 连接错误: ' . $e->getMessage() . ' 在 ' . $e->getFile() . ' 文件的第 ' . $e->getLine() . ' 行');
      exit();
    }
  

有了这个文件后,我们只需在任何需要数据库连接的 PHP 脚本中包含它即可。要是数据库由于某种原因连接失败,将会打印一条消息并终止脚本。

  
    require_once 'database_connection.php';
  

包含该文件后,我们就可以使用 $mysqli 变量来执行所需的任何数据库操作。

四、创建用户表

现在我们已经建立了数据库连接,接下来需要创建一个表来存储认证系统的用户信息。我们不会把所需的 SQL 代码复制粘贴到命令提示符中,而是创建一个脚本来设置表并添加几个用户。

  
    // 包含数据库连接。
    require_once 'database_connection.php';

    // 删除 'users' 表(如果存在)。
    $mysqli->query("DROP TABLE IF EXISTS `users`;");

    // 创建 'users' 表。
    $sql = "CREATE TABLE `users` (
        `id` INT NOT NULL AUTO_INCREMENT,
        `username` varchar(255) NOT NULL COMMENT '用户的用户名。',
        `password` varchar(255) NOT NULL COMMENT '用户的密码。',
        `name` varchar(255) NULL DEFAULT '' COMMENT '用户的姓名。',
        PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='存储用户认证详细信息。';";
    $mysqli->query($sql);
  

表创建完成后,我们可以添加几个测试用户。

在创建用户时,我们会通过 password_hash() 函数处理他们的密码,该函数会创建他们密码的一次性哈希值。这意味着我们不是以明文形式存储密码,而是存储密码的哈希版本。哈希函数会对字符串进行一系列处理,将其转换为看起来随机的字符串。关键特性是相同的字符串总是会返回相同的哈希值,并且从哈希值中无法还原出原始字符串。

例如,使用 bcrypt 哈希算法,密码“password”将转换为哈希值“$2y$10$RCnjJFeAVAbrSkTaYVkj3.IHZF0vC9WgOoQrcYNB.xxtc0wqgitYS”。

这看似是多余的额外步骤,但实际上是任何认证系统安全的重要环节。以单向哈希形式存储密码意味着,如果数据库泄露(或以任何方式受损),没有人能够从数据库中的哈希值重新创建密码。

如果你想了解更多关于 PHP 密码函数的信息,可进一步查阅相关资料。

以下是在数据库中创建两个用户的代码。

  
    // 生成两个测试用户。
    $userData = [
        [
            'user1',
            password_hash('password', PASSWORD_DEFAULT),
            'User One',
        ],
        [
            'user2',
            password_hash('letmein', PASSWORD_DEFAULT),
            'User Two',
        ],
    ];

    $stmt = $mysqli->prepare("INSERT INTO `users`(`username`, `password`, `name`) VALUES (?, ?, ?);");

    foreach ($userData as $id => $userDatum) {
        $stmt->bind_param("sss", ...$userDatum);
        $stmt->execute();
    }

    echo '认证示例表已(重新)创建并安装了默认用户。' . PHP_EOL;
  

默认情况下,当我们使用 password_hash() 函数时,还能受益于 密码盐 的添加。

密码盐是在哈希之前添加到密码中的一个值;登录时也会将相同的值添加到密码中,以便与哈希值匹配。虽然用户的密码以哈希形式保存,但仍然有可能通过暴力破解原始字符串来匹配哈希值。如果攻击者设法获取了用户的哈希密码,他们最终可能会通过尝试大量不同的字符串,直到找到匹配的字符串来破解密码。

这看似不太可能,但当一篇研究论文证明较旧的 MD5 哈希算法可以在一台相当普通的计算机上仅用 5 分钟就被暴力破解时,该算法就被淘汰了。这种方法还有很多不同的技巧,你可以使用已知密码的“彩虹表”来减少测试的字符串数量,这些密码更有可能是正确的。

添加盐值(越长越好)会使攻击者的任务变得更加困难,因为他们需要同时猜测密码和盐值才能找到匹配的哈希。由于我们在 password_hash() 函数中没有使用标准盐,因此 password_hash() 会为每个哈希的密码生成一个随机盐。这是预期的操作模式。

完成上述所有操作后,我们只需在 DDEV Docker 容器内使用 PHP 解释器运行 install.php 文件。

  
    ddev exec php install.php
  

或者,我们也可以登录到 DDEV Docker 容器并以这种方式运行安装脚本。

  
    ddev ssh
    php install.php
  

运行安装脚本后,它将为我们的认证系统创建所需的用户。

五、使用 Bootstrap 进行主题设计

在开始开发应用程序的功能之前,咱们先快速思考一下如何为其设计主题。

这个网站的主题将由一个名为 Bootstrap 的 CSS 框架来处理。这意味着我们无需为系统添加大量自定义样式,只需为 HTML 元素添加一些类即可为应用程序添加样式。

要将 Bootstrap 包含到网站中,我们只需在网站的头部添加以下代码行。

  
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
  

我不会深入介绍如何使用 Bootstrap,但让我们详细分析其中一个元素。

以下是构成网站头部的 HTML 代码。

  
    <div class="container">
        <header class="d-flex flex-wrap py-3 mb-4 border-bottom">
            <a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
                <span class="fs-4">认证示例</span>
            </a>
        </header>
    </div>
  

第一个 div 元素有一个名为“container”的类。

  • container - HTML 容器是 Bootstrap 中最基本的布局元素,在使用某些布局系统(如网格)时是必需的。在这种情况下,我们为响应式布局中的每个不同断点设置 max-width

内部的 header 元素有以下类。

  • d-flex - 这将元素的显示模式设置为 flex
  • flex-wrap - 使 flex 容器中的元素在需要时 换行 到下一行。
  • py-3 - 将元素的顶部和 底部内边距 设置为通过将 1rem 乘以 1.5 计算得出的值。
  • mb-4 - 将元素的 底部外边距 设置为通过将 1rem 乘以 1.5 计算得出的值。
  • border-bottom - 在元素的底部添加一个 边框

最多的类被应用到 a 元素上。

  • d-flex - 这将元素的显示模式设置为 flex
  • align-items-center - 将此元素内的元素居中对齐。
  • mb-3 - 将元素的 底部外边距 设置为 1rem
  • mb-md-0 - 当浏览器窗口宽度小于 768px 时,将元素的 底部外边距 设置为 0
  • me-md-auto - 将元素末尾的右侧 外边距 设置为 auto。这使我们可以添加多个元素并保持列表中最后一个元素的外边距一致。
  • text-dark - 使文本颜色为深灰色。这使该元素在页面上的其他黑色文本中更加突出。
  • text-decoration-none - 关闭该元素的任何文本装饰。由于这是一个 a 元素,否则它会有下划线装饰,因此此规则关闭了该装饰。

最后,内部的 span 元素只有一个类。

  • fs-4 - 将元素的 字体大小 设置为 1rem 乘以 1.5。

将所有这些组合在一起,页面上会显示如下的头部。

PHP 认证应用程序头部的截图。

这个头部将在整个项目中使用,以使项目具有统一的体验。而且我们没有编写任何自定义 CSS 来实现这一点。

如果你想了解更多关于 Bootstrap 的信息,请查看相关文档页面。我不会详细介绍本项目中使用的所有不同类,但如果你想了解更多,该文档网站是一个很好的起点。

当你想要加快网站的开发速度时,Bootstrap 通常很有用,因为样式可以轻松快速地应用到任何标记上。缺点是你很容易创建出一个明显使用 Bootstrap 创建的网站。自定义主题以更改这些样式通常相当困难;通常最好扩展现有样式并添加自己的类。

现在我们的样式已经设置好,我们可以开始开发网站的页面,并只需添加类即可轻松使它们看起来美观。

六、创建登录表单

登录表单很简单。在 login.php 文件中,我们只需添加以下 HTML 标记。这将生成一个包含用户名和密码字段的表单。还有一个提交按钮,允许用户提交表单。

  
    <form action="login.php" method="post">
        <h2>登录</h2>

        <div class="form-outline mb-4">
            <label class="form-label" for="form-username">用户名</label>
            <input type="text" name="username" placeholder="Username" id="form-username" autocomplete="autocomplete" class="form-control" value="<?php echo $_POST['username'] ?? '';?>">
        </div>

        <div class="form-outline mb-4">
            <label class="form-label" for="form-password">密码</label>
            <input type="password" name="password" placeholder="Password" id="form-password" autocomplete="autocomplete" class="form-control">
        </div>

        <button type="submit" class="btn btn-primary btn-block mb-4">登录</button>
    </form>
  

这将创建以下表单,并包含上一步中添加的头部元素。

认证应用程序登录表单的截图。显示了表单的密码、用户名和提交部分。

请注意,“用户名”字段将自动更新用户输入的值,这有助于用户在密码输入错误时继续操作。

此表单的动作是 login.php,这意味着表单将重新加载自身,这种技术称为表单回发。

七、用户认证

现在我们有了登录表单,就可以开始对用户进行认证了。

表单提交后,页面将重新加载,并在 $_POST 超全局变量中创建我们可以检测到的值。$_POST 超全局变量是一个特殊的数组,包含有关表单提交的信息,这意味着我们可以通过查看这些值来检测用户是否填写了表单。如果 $_POST['username']$_POST['password'] 数组项包含信息,那么我们可以说表单已提交。

顺便说一下,PHP 有许多超全局变量,包括 $_GET、$_FILES、$_REQUEST、$_SESSION、$_ENV 和 $_COOKIE。在这个应用程序中,我们将使用 $_POST 和 $_SESSION,但值得查看其他超全局变量,以确保你了解它们的作用。

以下是我们添加到页面顶部以检测表单提交的代码块。

  
    // 初始化错误变量。
    $error = '';

    if (isset($_POST['username']) && isset($_POST['password'])) {
      // 用户已输入凭据,去除首尾空格以防止常见的空格错误。
      $username = trim($_POST['username']);
      $password = trim($_POST['password']);

      // 包含数据库连接。
      require_once '../database_connection.php';

      // 尝试对用户进行认证。
      $authenticationSuccessful = checkAuthenticationDetails($username, $password, $mysqli);

      // ... 对用户的认证尝试做出反应。
    }
  

利用表单提交的数据,我们可以提取这些数据并将其传递给一个函数,该函数将检查用户是否能够进行认证。

我们调用名为 authenticate 的函数,并将用户名、密码和数据库连接传递给它。该函数的任务是从数据库中查找用户记录,并将数据库记录中的哈希密码与用户刚刚输入的密码的哈希值进行比较。如果两个哈希值相同,那么我们可以说用户输入了正确的密码。

以下是完整的 checkAuthenticationDetails() 函数。

  
    /**
     * 根据数据库中的值检查用户凭据。
     *
     * @param string $username
     *   提供的用户名。
     * @param string $password
     *   提供的密码。
     * @param mysqli $mysqli
     *   MySQL 连接。
     *
     * @return bool
     *   如果用户账户认证成功,则返回 true。
     */
    function checkAuthenticationDetails(string $username, string $password, mysqli $mysqli): bool
    {
      // 确保用户名和密码字段中有内容。
      if ($username === '' && $password === '') {
        // 由于用户名和密码字段为空,我们返回 false 表示认证尝试未成功。
        return false;
      }

      // 根据用户名在数据库中搜索用户。
      $stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?;");
      $stmt->bind_param("s", $username);
      $stmt->execute();
      $result = $stmt->get_result()->fetch_assoc();

      if ($result === null) {
        // 如果结果为空,则用户不存在。我们返回 false 表示认证尝试未成功。
        return false;
      }

      // 根据数据库中的哈希密码验证提供的密码。
      if (password_verify($password, $result['password']) === false) {
        // 用户账户存在,但提供的密码验证不正确,即不匹配。返回 false 表示认证尝试未成功。
        return false;
      }

      // 最后,返回 true 以表示认证成功。
      return true;
    }
  

checkAuthenticationDetails() 函数只做一件事,即根据用户的凭据返回一个布尔值。如果该函数返回 true,我们可以让用户登录;如果返回 false,则用户无法登录。编写只做一件事的函数是很好的实践,因为这会使代码更易于处理。

登录过程包括将用户的用户名添加到 $_SESSION 超全局变量中,这样在下次页面加载时该信息仍然有效。我们将在下一节详细介绍这一点,但一旦我们将用户信息添加到 $_SESSION 超全局变量中,我们就会使用 header() 函数将用户重定向到 index.php 页面,然后停止执行任何进一步的代码。

  
    if ($authenticationSuccessful) {
      // 用户认证成功,因此我们从数据库中加载他们的用户详细信息并将其存储在 $_SESSION 变量中。会话超全局变量中存在用户名表示他们已正确登录。
      $stmt = $mysqli->prepare("SELECT id, name FROM users WHERE username = ?;");
      $stmt->bind_param("s", $username);
      $stmt->execute();
      $result = $stmt->get_result()->fetch_assoc();

      $_SESSION['username'] = $username;
      $_SESSION['name'] = $result['name'];
      $_SESSION['user_id'] = $result['id'];

      // 将用户重定向回主页。
      header("Location: index.php");
      exit();
    } else {
      // 认证未成功,更新错误变量,以便我们可以向用户显示一条消息。
      $error = '用户名或密码不正确。';
    }
  

如果用户不存在或密码不匹配,checkAuthenticationDetails() 函数将返回 false。在这种情况下,我们设置一个错误字符串,然后可以将其打印出来,让用户知道为什么无法登录。最好告诉用户他们的认证信息存在一般性问题,而不是具体说明是什么类型的错误。这样可以减少攻击者获取的信息,从而降低他们破解系统或猜测用户账户数量的可能性。

可以像这样打印出该函数返回的错误消息。

  
    <form action="login.php" method="post">
        <h2>登录</h2>
        <?php if ($error !== '') { ?>
            <p class="alert alert-danger"><?php echo $error; ?></p>
        <?php } ?>
  

最后,我们需要在文件开头添加几个额外的组件。调用 session_start() 是必不可少的,这样我们才能在 PHP 中访问会话和会话数据。在访问会话之前必须调用此函数,因此将其添加到每个页面的顶部。

  
    // 启动会话以包含会话变量。
    session_start();
  

然后,我们需要确保任何已认证的用户访问此页面时不会再次看到登录表单,因此,如果用户有会话数据,我们添加一个简单的重定向将用户推回到 index.php 文件。用户登录应用程序时会添加 user_id,因此如果该值存在,我们可以说用户已登录。

  
    if (isset($_SESSION['user_id'])) {
      // 如果 'user_id' 会话变量存在,则用户已经过认证。在这种情况下,我们将他们重定向到主页。
      header("Location: index.php");
      exit();
    }
  

现在登录页面允许用户认证进入系统,让我们看看如何保护页面。

八、保护页面

现在我们有了登录表单,需要一种方法来保护网站上的页面。这可以通过检测 $_SESSION 超全局变量中是否存在用户的 ID 来实现。如果设置了该值,我们可以说用户已成功通过认证函数,并创建了一个有效的会话。

PHP 中每个需要与会话系统交互的页面都必须以调用 session_start() 开始。这将初始化会话,并允许我们存储和检索会话数据。

  
    <?php

    // 启动会话以包含会话变量。
    session_start();

    if (!isset($_SESSION['user_id'])) {
      // 如果 'user_id' 未设置,则用户未认证。将他们重定向到 login.php 页面。
      header('Location: login.php');
      exit();
    }
    ?>
  

如果该参数不存在,我们将用户重定向回登录页面。

九、欢迎用户

由于用户有一个活跃的会话,我们可以对其会话中的数据进行一些有趣的操作。当用户登录时,我们向 $_SESSION 超全局变量中添加了一些详细信息,这样我们就可以在受保护的页面上访问这些数据。

例如,要从 $_SESSION 中打印出用户的姓名,我们编写以下代码。

  
    <p>欢迎 <?php echo htmlspecialchars($_SESSION['name']); ?></p>
  

这创建了一个非常简单的个性化功能,仅使用了用户登录时创建的会话变量。

十、注销登录

允许用户登录网站固然重要,但同样重要的是允许他们注销。

为此,我们只需创建一个名为 logout.php 的文件,并添加以下代码。这将使用 session_start() 初始化会话,然后使用 session_unset() 清除所有会话数据。为了保险起见,我们还调用 session_destroy(),它将清除与此会话关联的所有会话数据。最后,我们将用户重定向回应用程序的主页。

  
    <?php

    // 启动会话以包含会话变量。
    session_start();

    // 取消设置为此会话创建的任何会话变量。
    session_unset();

    // 销毁会话。
    session_destroy();

    // 将用户重定向到首页。
    header("Location: index.php");
  

有了这个文件后,我们只需添加一个链接,让用户能够注销网站。

  
    <a href="/logout.php">注销登录</a>
  

当用户点击此链接时,他们将从应用程序中注销。

十一、运行完整项目

整个应用程序可以由上述代码组合而成,或者你也可以获取代码源进行查看。启动并运行该应用程序相当简单,不过前提是你已经安装了 Git 和 DDEV 等必备软件包。

要获取代码库,打开命令终端,使用 "git clone" 命令,并传入相应项目的 Git 仓库地址。

  
    git clone git@github地址
  

使用 git clone 命令创建目录后,切换到该目录。

  
    cd 项目目录名
  

就 DDEV 而言,一切都已经配置好,所以你只需使用 "ddev start" 命令启动它。

  
    ddev start
  

一切运行起来后(可能需要几分钟),我们需要使用 install.php 脚本来创建用户表和系统用户。运行以下命令来完成此操作。

  
    ddev exec php install.php
  

这就是让网站启动并运行所需的全部操作。

现在你可以访问本地网站,使用以下密码登录系统。

用户名 密码
user1 password
user2 letmein

登录并在网站上浏览。有几个受保护的页面需要你登录才能查看。

十二、总结

现在我们有了一个认证系统,可以保护少数页面不被未经授权的访问。虽然功能齐全,但这是一个相当简单的系统,没有太多额外的功能。有一些非常基本的模板化操作,因为对于所有已认证的页面,登录后的头部部分会被注入到网站中。

可以向这个系统添加一些更多的功能,以使其更加健壮:

  • 跨站脚本攻击防护 - 在当前状态下,登录表单有可能被其他网站“触发”。这可能成为攻击者的攻击途径,他们可以通过创建钓鱼网站来窃取用户凭据。钓鱼网站只需记录用户输入的信息,并将其传递给真正的应用程序,从而使用户正常登录。有了这种防护措施,在没有告知用户发生错误的情况下,就无法进行此类攻击。
  • 暴力破解防护 - 如果攻击者想要闯入系统,他们只需猜测用户名和密码。目前该系统没有针对这种攻击的防护措施,因此攻击者只需要时间,每分钟发送数千个请求,直到其中一个成功。暴力破解防护的作用是,如果用户多次未能正确输入密码,则锁定他们的账户,使其无法被破解。还可以封锁 IP 地址,以防止此类攻击影响网站上的多个用户。
  • 数据库会话存储 - 我们在这里没有涉及的一个很好的功能是让 PHP 将其会话存储在数据库中。默认情况下,PHP 将其会话作为文件存储在服务器的文件系统中。将这些会话移动到数据库中,我们可以在用户会话数据中存储更多信息。这还允许我们管理网站上的会话,因此如果我们想让所有人注销,只需清空会话表即可。基于文件的 PHP 会话在使用负载均衡的网站上无法正常工作,除非已采取措施在所有 Web 节点之间共享会话文件。如果没有这样做……(文档此处似乎未完成)