七、认证与用户管理

在上一章中,我们更好地理解了database类在项目中所扮演的角色,开发人员每次与数据库交互时都会用到这个角色。

我们使用的唯一一个库是 Whoops,它将以可读的格式显示错误。我们还获得了构建默认状态的经验,包括baseControllerbaseMethod

在本章中,我们将重点关注项目的安全方面,即身份验证。我们将构建与数据库交互的登录表单,以验证用户的身份。最后,我们将介绍如何在应用中设置密码恢复机制。

在本章结束时,您将能够:

  • 为其应用生成默认视图
  • 建立密码管理和重置系统
  • 为系统应用内的模块构建 CRUD

设置路径和包含引导

在本节中,我们将继续在框架之上构建功能。核心框架系统文件已到位。此设置用于在此基础上构建有用的功能。

我们将构建认证系统并完成应用构建。需要身份验证以防止未经授权的用户访问。这确保只有具有有效用户名和密码的用户才能登录到我们的应用。

在本章中,我们将介绍身份验证。请注意,本课程中使用的所有示例的登录用户名和密码如下:

用户名:demo

密码:demo

设置路径并创建文件目录的绝对路径

相对路径是相对于当前文件夹路径的路径,例如。/css指向一个文件夹向上进入css文件夹的相对路径。

绝对路径是指向文件或文件夹的完整路径,如/user/projects/mvc/css.

这很重要,因为这将允许使用框架系统中任何位置的绝对路径包含文件。这是对系统中现有代码的自适应。

例如:

$filepath = "../app/views/$path.php";

这就变成了:

$filepath = APPDIR."views/$path.php";

此建立在当前概念的基础上,允许将视图组织到子文件夹中。如果没有这种调整,就不可能将任何内容组织到子文件夹中,这将妨碍保持代码的整洁组织。

在没有这些更改的情况下,可以继续构建系统,但确保代码整洁有序始终是一个好主意。

创建布局文件

需要布局文件,以便显示任何错误。

此外,headerfooternavigation需要布局文件。一旦创建,这些文件将提供应在整个应用中引入的元素。这将包括全局元素。

Creating Layout Files

错误用于验证,这将在下一小节中介绍,不要与解析错误或前面看到的类似错误混淆。这些步骤涉及的错误是与表单验证相关的错误,用户在表单字段中输入了不正确的信息。

包含引导

Bootstrap 是一个 HTML、CSS 和 JavaScript 库,在本章中,它将提供基本的样式。它对开发人员很有用,因为它可以帮助他们在设计人员将设计元素添加到应用之前对应用进行原型化和可视化。

在本项目中,Bootstrap 将作为一个内容交付网络CDN包含在标题中。CDN 获取 web 上常见的资源并缓存它们以帮助提高性能。

这很容易与引导框架混淆。

Bootstrap、HTML、CSS 和 JavaScript 库以及 Bootstrap 的概念是两个不同的东西,它们具有相似的名称。

您可以通过访问以下链接找到有关引导的更多信息:https://getbootstrap.com/

包含引导和 HTML 标记

本节的目的是实现我们已经实现的通用样式,它显示了引导和 HTML 标记的包含:

Inclusion of Bootstrap and HTML Markup

路径中尚未解决的问题。到目前为止,我们一直在使用相对路径来包含文件,比如system/View.php.中的视图,让我们修复一下:

  1. Open webroot/index.php and add these lines after line 9:

    php defined('DS') || define('DS', DIRECTORY_SEPARATOR); define('APPDIR', realpath(__DIR__.'/../app/') .DS); define('SYSTEMDIR', realpath(__DIR__.'/../system/') .DS); define('PUBLICDIR', realpath(__DIR__) .DS); define('ROOTDIR', realpath(__DIR__.'/../') .DS);

    这些是可以在框架中的任何位置调用的常量。第一行定义目录分隔符,例如,/\,具体取决于机器:

    • APPDIR–指向app文件夹
    • SYSTEMDIR–指向system文件夹
    • PUBLICDIR–指向webroot文件夹
    • ROOTDIR-指向root项目路径

    每一个都会创建到其端点的绝对路径。

  2. Now, let's fix the View class. Open system/View.php, and on line 24, replace:

    php $filepath = "../app/views/$path.php";

    与:

    php $filepath = APPDIR."views/$path.php";

    这允许视图包括来自父文件夹或子文件夹的其他视图,而不会出现任何问题。

  3. 接下来,在app/views.内创建一个名为layouts的文件夹,在app/views/layouts内创建以下文件:

    • errors.php
    • footer.php
    • header.php
    • nav.php
    • errors.php
  4. Open errors.php and enter the following code:

    ```php <?php use App\Helpers\Session;

    if (isset($errors)) { foreach($errors as $error) { echo "

    $error
    "; } }

    if (Session::get('success')) { echo "

    ".Session::pull('success')."
    "; } ```

    这包括一个会话助手,我们将很快创建它。

    第一条if语句检查$errors是否存在,如果存在,则退出循环并显示警报。这些类是Bootstrap类(我们将在header.php中提供)。

    下一个if语句检查名为success,的会话是否存在,如果存在,则显示其内容。这用于向用户提供反馈。

  5. Open header.php and enter the following code:

    ```php <!doctype html>

    <?=(isset($title) ? $title.' - ' : '');?> Demo</ title>

    ```

    这将设置 HTML 文档,如果存在,还可以选择使用$title,。还包括引导 CDN CSS 和 JavaScript,以及 jQuery 和位于webroot/css/style.css中的自定义 style.CSS 文件–创建此文件。

  6. Now, open footer.php and close the container div and the body and html tags:

    有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

    php </div> </body> </html>

  7. Now, open nav.php and enter the following code:

    php <nav class="navbar navbar-default"> …… </div><!--/.nav-collapse --> </div><!--/.container-fluid --> </nav>

    这是用于引导的导航组件。这是一个干净的方式来为我们的管理页面带来一个响应菜单。请注意两个页面链接,分别是管理员和用户。我们还将提供一个注销链接。

  8. Now, open app/views/404.php and include the layout files:

    php <?php include(APPDIR.'views/layouts/header.php');?> 404! <?php include(APPDIR.'views/layouts/footer.php');?>

    这将引入页眉并显示页面内容,最后包含页脚。

    这里不要包括nav。即使用户未登录,也可以显示 404。

    这为将常见布局组织到视图中提供了一种非常简洁的方法,这样当您需要更改全局元素时,布局视图就是它们的存储位置。

  9. Open the framework in the browser if it's not already running. Run the following command from Terminal when on the root:

    php php –S localhost:8000 –t webroot

    您不会注意到任何不同,但您将被重定向到一个不存在的页面:http://localhost:8000/example

  10. You'll see a 404 page that includes the header and footer layouts. Look at the page source code – right-click and click on 'view page source'. You should see the following output:

    有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

    php <!doctype html> <html lang="en"> <head> …… 404! </div> </body> </html>

  11. 随着本章的深入,这些布局将变得更加明显。

    在本节中,我们介绍了如何正确设置文件路径。我们讨论了如何正确设置引导,最后我们为错误和全局元素(如页眉、页脚、导航和错误)设置了视图。

    在下一节中,我们将介绍如何为应用添加安全性并设置密码恢复。

    增加项目安全性

    在本节中,我们将继续在框架之上构建特性。核心框架系统文件已就位。

    本节的目标是构建将为项目增加安全性的功能。我们将介绍在应用中维护良好安全性所需的各个方面。

    帮手

    在本小节中,我们将介绍helpers.

    我们还将创建一个URL``helper和一个session``helper。这些将有助于身份验证以及系统的任何其他方面,但与身份验证没有直接关系。

    会话助手是 PHP 会话的wrapper,包括开发人员在处理会话时有用的各种方法。

    URL``helper非常相似,因为它是处理 URL 的有用方法。然而,在本书中,它要短得多,并且仅限于一种方法。

    session是一种存储临时数据的方法,比如用户是否登录。

    认证

    现在,我们将构建身份验证功能。身份验证是一种仅允许具有正确凭据的人访问受限分区的方法。

    这将涉及创建数据库表和模型:

    • 在数据库中创建用户表
    • 在应用模型中创建用户模型
    • 添加插入、更新和删除方法

    然后,我们将创建一个管理控制器,并导入 URL 和session助手以及user模型。

    最后,我们将创建关联的视图。

    仪表板

    项目需要一个仪表盘;这就像一个项目的主页,需要登录,通常包括指向项目经常访问的内容的链接。在这个项目中,我们只需要确保仪表板有一个存在的文件,以便它可以被定向到它。您将创建仪表板视图,包括布局文件以及页眉、页脚、导航和错误。您将为页面结构添加 HTML。

    登录

    登录页面的创建也是本节的一部分。

    在“登录”视图中,您将创建一个登录表单,并包括布局文件。

    然后,他们将创建一个登录方法来处理登录过程:

    • 该过程的一部分是使用 passwordhash 和 bcrypt 对密码进行散列
    • 使用设计用于返回数据的 Get data 方法
    • 除了创建视图和登录方法外,我们还将创建logout方法,并修改配置,使主页在默认情况下成为管理仪表板

    密码散列

    密码散列使用 bcrypt,这是可用的最强大的算法。目前,破解密码散列平均需要 12 年的时间。

    该过程的一部分是验证数据,检查用户名和密码是否与数据库中存储的内容匹配。

    密码散列是从您的密码创建一个字符串作为单向散列,任何用户都不能确定散列的原始内容。

    密码散列不能与加密混淆。不同之处在于,在密码散列中,可以将散列后的密码解密为其原始状态。

    在 PHP 中实现验证

    在本节中,我们将看到以下结果。

    Implementing Validation in PHP

    本节展示了如何在 PHP 中实现验证,尽管它还不能正常工作,因为我们还没有创建和提供构成系统知识的数据源。

    要作为本节的一部分解决此问题,我们将手动创建一个用户。

    按照以下步骤在 PHP 中实现验证:

    创建助手:

    1. Before we can start building the authentication, we need two new helpers. In app/Helpers, create a new file called Url.php and enter:

      ```php <?php namespace App\Helpers;

      class Url { public static function redirect($path = '/') { header('Location: '.$path); exit(); } } ```

      这提供了一个名为 redirect 的方法,该方法默认为/当未传递任何参数时。这是重定向到应用另一页的简单方法。

      要在将类包含到页面中后使用该类,请使用:Url::redirect('url/to/redirect/to')

      要重定向到主页,请使用:

      Url::redirect()

      接下来,我们需要一种使用会话的方法。会话是 PHP 从一页到另一页跟踪数据的一种方式,这非常适合我们的需要,例如能够通过读取会话数据来检测用户是否登录。

      我们可以使用普通的$\u 会话调用,但是因为我们使用的是 OOP,所以让我们利用这一点构建一个会话助手。

    2. app/Helpers内创建一个名为Session.php的文件。

    3. First, set the namespace and class definition:

      需要的第一种方法是确定会话是否已启动。如果更新了sessionStarted参数,则会将其设置为false. This并告知init方法开启会话:

      ```php <?php namespace App\Helpers;

      class Session { private static $sessionStarted = false; /* * if session has not started, start sessions / public static function init() { if (self::$sessionStarted == false) { session_start(); self::$sessionStarted = true; } } ```

    4. 接下来,创建一个名为set的方法,该方法接受两个参数$key$value.,用于将$key添加到会话中,并将$value设置为$key:

      php public static function set($key, $value = false) { /** * Check whether session is set in array or not * If array then set all session key-values in foreach loop */ if (is_array($key) && $value === false) { foreach ($key as $name => $value) { $_SESSION[$name] = $value; } } else { $_SESSION[$key] = $value; } }

    5. 接下来,用一个参数创建一个名为pull的方法。这将从会话中提取key并在将其从会话中删除后返回,这对于一次性消息非常有用:

      php public static function pull($key) { $value = $_SESSION[$key]; unset($_SESSION[$key]); return $value; }

    6. Next, create a get method. This will return a session from the provided key:

      ```php public static function get($key) { if (isset($_SESSION[$key])) { return $_SESSION[$key]; }

      return false;
      

      } ```

      有时,您希望查看会话的内容。创建一个名为display的方法,该方法返回$_SESSION对象:

      php public static function display() { return $_SESSION; }

    7. The last method is used to destroy the session key when the $key is provided, otherwise the entire session will be destroyed:

      php public static function destroy($key = '') { if (self::$sessionStarted == true) { if (empty($key)) { session_unset(); session_destroy(); } else { unset($_SESSION[$key]); } } }

      完整的类如下所示:

      有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

      ```php <?php namespace App\Helpers;

      class Session { private static $sessionStarted = false; …….. }

      } ```

    8. Now, we need to set sessions automatically when the application runs. We do this by adding Session::init() inside app/Config.php:

      这使用了一个Use语句,包括对session's``helper类的调用。在这个阶段,突出这些 OOP 特性可能是有益的。

      有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

      ```php <?php namespace App;

      use App\Helpers\Session;

      class Config { ……. ]; } } ```

    楼宇认证:

    我们现在已经准备好开始构建管理员控制器和用户模型,这将是用户登录的入口点。

    1. Create a new table in your database called users:

      php CREATE TABLE `users` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, `created_at` datetime DEFAULT NULL, `reset_token` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

      ID 是primary键,将设置为自动递增,这意味着每条记录都有一个唯一的 ID。

      reset令牌仅在需要重置密码程序时使用。

    2. 让我们从模型开始。在app\Models内创建一个名为User.php的文件。

    3. Set the namespace and import the base Model and set the class definition.

      我们将回到这个模型,并根据需要添加必要的方法。

    4. Add methods for inserting, updating, and deleting records:

      有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

      php <?php namespace App\Models; ……. { $this->db->delete('users', $where); } }

    创建管理控制器:

    1. Now, create a new file in app/Controllers called Admin.php.

      这将是登录和退出管理仪表板的入口点。

    2. 设置名称空间,导入baseControllerSessionURL``helpers以及User模型。

    3. Set the class definition and create a property called $user. Then, in the __construct method, initialize the User Model by calling new User().

      这意味着要访问用户模型的任何方法,都可以使用$this->user

      下一种方法是index()。只要用户登录,就会加载仪表板视图。

    4. To ensure that the user is logged in, an if statement is run to check for the existence of a session key called logged_jn, which is set only after logging in. If the user is not logged in, then redirect them to the login method:

      有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

      ```php <?php namespace App\Controllers;

      use System\BaseController; …….. $this->view->render('admin/index', compact('title')); }

      } ```

    5. If the user is logged in, then the admin/index view will be loaded. Create the view app/views/admin/index.php and the entry:

      ```php <?php include(APPDIR.'views/layouts/header.php'); include(APPDIR.'views/layouts/nav.php'); include(APPDIR.'views/layouts/errors.php'); ?>

      Dashboard

      This is the application dashboard.

      <?php include(APPDIR.'views/layouts/footer.php');?> ```

      现在,我们需要创建一个login视图。在app/views/admin内创建名为auth的文件夹,并创建login.php.

    6. 首先,包括header布局,然后创建一个调用方为wrapperwelldivwell类是一个引导类,它提供了灰色的正方形样式。wrapper类将用于定位div

    7. 接下来,包括布局以捕获任何错误或消息。
    8. 现在,我们将创建一个表单,该表单将有一个方法post将其内容发布到ACTION URL,,在本例中为/admin/login
    9. Then, create two inputs for the username and password. Make sure the input type for password is set to password.

      将输入类型设置为password将停止在屏幕上显示密码。

      提交表单时,输入的命名属性是 PHP 了解数据的方式。

      提交表单还需要提交按钮。一个好的做法是,如果用户记不起他们的登录详细信息,则提供重置选项。我们将创建一个链接,将用户指向/admin/reset

    10. Finally, close the form and include the footer layout:

      有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

      ```php <?php include(APPDIR.'views/layouts/header.php');?>

      <?php include(APPDIR.'views/layouts/errors.php');?>
      

      ……. .wrapper h1 { margin-top: 0px; font-size: 25px; } ```

    11. Now, go back to the admin Controller and create a login method:

      检查用户是否已登录,以重定向用户。当他们已经登录时,他们应该无法看到登录页面。

    12. Inside the login method, create an empty $errors array and set the page $title and load a view calling admin/auth/login, passing the $title and $errors variables by using a compact function.

      compact()只需输入变量名称而无需输入$:即可使用变量

      ```php public function login() { if (Session::get('logged_in')) { Url::redirect('/admin'); }

      $errors = [];
      
      $title = 'Login';
      
      $this->view->render('admin/auth/login', compact('title', 'errors'));
      

      } ```

      这将加载login视图,在按下 submit 时,实际上不会执行任何操作。我们需要检查提交的表单,但在此之前,我们需要在user模型中添加两种方法:

      php public function get_hash($username) { $data = $this->db->select('password FROM users WHERE username = :username', [':username' => $username]); return (isset($data[0]->password) ? $data[0]->password : null); }

      get_hash($username)将从users表中选择password,其中username与提供的匹配。

      设置username = :username将创建一个占位符。然后,[':username' => $username将使用该占位符,以便它知道值将是什么。

      然后检查$data[0]->password是否设置并返回。否则返回null

    13. 只在这一次对get_data(),再次执行相同的操作,返回一个数据数组而不是一列:

      php public function get_data($username) { $data = $this->db->select('* FROM users WHERE username = :username', [':username' => $username]); return (isset($data[0]) ? $data[0] : null); }

    14. 现在,在我们的login方法中,我们可以通过检查$_POST数组是否包含名为submit.的对象来检查表单是否已提交

    15. Then, collect the form data and store them on local variables. Using htmlspecialchars() is a security measure, since it stops script tags from being able to be executed and renders them as plaintext.

      接下来,运行一个调用password_verify(),if语句,该语句是一个内置函数,返回truefalse。第一个参数是用户提供的$password,,第二个参数是通过调用$this->user->get_hash($username)从数据库返回的哈希密码。只要password_verify等于false,,登录检查就失败了。

    16. 设置一个$errors变量以包含errors消息。接下来,计算$errors和,如果它等于0,,这意味着没有错误,所以从$this->user->get_data($username).获取用户数据,然后使用会话助手创建一个名为logged_in的会话密钥,其值为true,,另一个会话密钥的值为用户 ID。

    17. Finally, redirect the user to the admin index page:

      ```php if (isset($_POST['submit'])) { $username = htmlspecialchars($_POST['username']); $password = htmlspecialchars($_POST['password']); if (password_verify($password, $this->user->get_hash($username)) == false) { $errors[] = 'Wrong username or password'; } if (count($errors) == 0) { //logged in $data = $this->user->get_data($username); Session::set('logged_in', true); Session::set('user_id', $data->id);

                  Url::redirect('/admin');
              }
          }
      

      ```

      完整方法如下所示:

      php public function login() { if (Session::get('logged_in')) { Url::redirect('/admin'); } …… $this->view->render('admin/auth/login', compact('title', 'errors')); }

    18. 如果框架尚未运行,则运行该框架:

      php php –S localhost:8000 –t webroot

    19. Go to http://localhost:8000/admin/login.

      您将看到一个登录页面。无论您输入什么,按 login 将显示一条错误消息“【T0]”,因为数据库中当前没有用户。

    20. Let's create our login. We need a hashed password to store in the database. To create one in the login method, enter:

      php echo password_hash('demo', PASSWORD_BCRYPT);

      第一个参数是您想要的password,在本例中,demo.第二个参数是要使用的PASSWORD函数的类型。使用默认的PASSWORD_ BCRYPT意味着 PHP 将使用最强大的版本。

    21. 当您刷新页面时,您将看到如下所示的哈希:

      php $2y$10$OAZK6znqAvV2fXS1BbYoVet3pC9dStWVFQGlrgEV4oz2GwJi0nKtC

    22. 复制此记录并将新记录插入数据库客户端,并将 ID 列留空。这将使它自己变得更强大。

    23. 创建一个username and email并粘贴到hash. F或密码中,为created at部分输入一个有效的datetime,如 2017-12-04 23:04:00。
    24. 保存记录。现在,您将能够设置登录。
    25. Upon logging in, you'll be redirected to /admin.

      请记住注释掉或删除echo password_hash('demo', PASSWORD_BCRYPT),,否则哈希将始终显示。

    26. 当我们开始的时候,让我们继续添加注销功能。注销是指破坏已登录和user_id会话的情况。在Admin控制器中,创建一个名为logout的新方法。

    27. 在方法内部,销毁会话object,然后重定向到login页面:

      php public function logout() { Session::destroy(); Url::redirect('/admin/login'); }

    28. 现在,返回应用并单击右上角的logout。您将注销并返回login页面。

    29. Now, log back in. If you click on the Admin link, you will be taken to the default page. In this case, it would be better to load the admin as soon as you load the application. We can do this by setting the Admin Controller to be the default app/Config.php.

      查找以下内容:

      php 'default_controller' => 'Home'

      替换为:

      php 'default_controller' => Admin,

    30. Now, if you click on Admin (after reloading the page), you'll see the admin dashboard.

      曾经有一段时间,密码哈希的某些标准被认为是互联网安全的最高级别。但是,与大多数技术一样,它不可避免地被提供,这削弱了其前身的有效性。

      由于以下哈希系统不安全,因此应不惜一切代价避免使用它们:

      • MD5
      • 沙尔 1
      • Shar 2 56

      这些密码散列功能很弱,计算机现在功能强大,只需几秒钟就可以破解它们。

      在开发人员确定新项目的范围时,最好对代码进行梳理,以检查是否存在安全缺陷,如使用这些缺陷。

    31. 在本节中,我们了解了身份验证过程。我们已经了解了如何进行登录过程。我们已经学习了密码散列的过程。现在,我们有了构建、配置和路由框架功能的经验。

      在下一节中,我们将介绍密码恢复的概念,其中我们将在应用中设置重置密码的功能。

      密码恢复

      本节介绍如何设置重置密码的功能。密码重置非常重要,因为可能会出现用户忘记密码的情况。我们现在将构建一个密码恢复过程,类似于下图:

      Password Recovery

      在 web 上找到的通用密码恢复示例

      我们将在管理控制器中创建一个名为 reset 的方法。此过程加载一个视图,用户将在其中输入其电子邮件地址以请求电子邮件。在处理此问题时,将进行验证,以确保电子邮件地址有效且实际存在于系统中。

      这将检查电子邮件,确保其格式正确,并检查提供的电子邮件地址是否存在于名为 users 的数据库表中。

      第三方依赖 PHP 邮件程序介绍

      Introduction to a Third-Party Dependency PHP Mailer

      来自 PHP Mailer 的图像:https://github.com/PHPMailer

      我们将添加一个第三方依赖项,包括用于发送电子邮件的 PHP Mailer。

      PHP Mailer 的工作原理如下:

      1. Provided that the validation had passed, we will then use PHP Mailer to send an email with a token. The token will later be received over email and entered into a hidden field as part of a form, fulfilling a requirement for the validation process.

        令牌只是一个随机的字母和数字字符串。其思想是为用户生成一些独特的东西,以识别来自他们的请求。

      2. 该过程的下一部分是向用户发送电子邮件,当用户单击该电子邮件时,创建一个方法来处理该请求。这涉及到创建一个 ChangePassword 方法,该方法接受电子邮件提供的令牌,然后显示包含表单的视图。

      3. 接下来,在视图中,将在隐藏字段中重新发送令牌。此外,用户可以输入新密码并确认此密码。提交后,控制器将处理数据并对其进行验证。这包括确保令牌与用户帐户匹配,密码足够长,并且两个密码都匹配。
      4. 创建后,当更新付诸实施时,用户将能够自动登录到管理系统,而无需重新输入密码。

      这就节省了用户在重置密码后必须登录的时间。从技术上讲,这是一个用户体验设计更新,尽管您可以在这里看到 UX 更改不仅仅局限于设计师领域。

      PHP 邮件程序检查格式是否正确。在电子邮件的情况下,这将期望@符号出现。这只是验证检查的一个示例。PHP 内置了一些方法,以便确定正确的格式是有效的格式。

      为我们的应用建立密码重置机制

      为了完成认证系统,我们需要能够在忘记密码的情况下重置密码。以下是执行此操作的步骤:

      1. Admin控制器中创建一个名为reset的新方法。
      2. 再次检查用户是否已登录,如果已登录,请将其重定向回管理员。
      3. 在加载名为reset:

        ```php public function reset() { if (Session::get('logged_in')) { Url::redirect('/admin'); }

        $errors = [];
        
        $title = 'Reset Account';
        
        $this->view->render('admin/auth/reset', compact('title', 'errors'));
        

        } ```

        的视图之前,设置errors数组并设置页面标题 4. Now create a view called reset.php in app/views/admin/auth and enter:

        ```php <?php include(APPDIR.'views/layouts/header.php');?>

        <?php include(APPDIR.'views/layouts/errors.php');?>
        
        <h1>Reset Account</h1>
        
        <form method="post">
        

        … …

        <?php include(APPDIR.'views/layouts/footer.php');?> ```

        该表格将发布到相同的url /admin/reset。我们收集的唯一数据是电子邮件地址。电子邮件地址将用于在继续之前验证用户是否存在。

      4. 现在,回到Admin控制器上的复位方法。

      5. 首先,检查表单是否已提交和isset,并输入提交按钮名称:

        php if (isset($_POST['submit'])) {

      6. 接下来,确保电子邮件地址为isset,,否则默认为null.检查电子邮件地址的格式是否正确:

        ```php $email = (isset($_POST['email']) ? $_POST['email'] : null);

        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {

        $errors[] = 'Please enter a valid email address';
        

        } else { if ($email != $this->user->get_user_email($email)){ $errors[] = 'Email address not found'; } } ```

      7. Lastly, check if the email address belongs to an existing user. To do this, create a new method in the user Model called get_user_email($email):

        如果存在,则返回电子邮件地址,否则返回null

        php public function get_user_email($email) { $data = $this->db->select('email from users where email = :email', [':email' => $email]); return (isset($data[0]->email) ? $data[0]->email : null); }

        在前面的控制器中,我们有:

        php if ($email != $this->user->get_user_email($email)){

        这将检查表单中提供的电子邮件地址是否与数据库不匹配,在这种情况下会创建新的错误。

      8. 验证检查后,没有错误:

        php if (count($errors) == 0) {

      9. Save the file; the method so far looks like this:

        有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

        ```php public function reset() { …….

        $this->view->render('admin/auth/reset', compact('title', 'errors'));
        

        } ```

        此时,除其他事项外,需要发送一封电子邮件。

        最佳做法是不使用 PHP 内置的mail(``)函数,而是使用phpmailer等库 https://github.com/PHPMailer/ 相反。

      10. 打开需求列表中的composer.jsonphpmailer

        php { "autoload": { "psr-4": { "App\\" : "app/", "System\\" : "system/" } }, "require": { "filp/whoops": "^2.1", "phpmailer/phpmailer": "~6.0" } }

      11. 保存文件并在终端中键入composer update。这将引入phpmailer,,使其可用于我们的应用。

      12. Admin控制器顶部,导入phpmailer

        php use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception;

      13. 接下来,转到下面的if语句中的reset方法。这是我们继续的地方:

        ```php if (count($errors) == 0) {

        } ```

      14. 现在,我们需要做一个随机标记。为此,使用md5,``uniqid,rand生成一个随机令牌。

      15. Then, set up a data and where array. The $data will specify the reset_token to have a value of $token, and the $where will be the email address. Pass them to the update() method of the user Model to update the user.

        这将根据数据库中的用户记录存储$token

        php $token = md5(uniqid(rand(),true)); $data = ['reset_token' => $token]; $where = ['email' => $email]; $this->user->update($data, $where);

      16. 现在,我们通过创建一个新的phpmailer实例来设置要发送的电子邮件,然后设置电子邮件的来源。根据需要更改此选项。

      17. 通过将 true 传递给 isHTML():

        php $mail = new PHPMailer(true); $mail->setFrom('noreply@domain.com'); $mail->addAddress($email); $mail->isHTML(true);

        来传递将要发送到的$email地址,并将模式设置为 HTML 19. 设置主题和电子邮件正文。我们提供两个主体:HTML 主体和纯文本主体。在用户的电子邮件客户端无法呈现 HTML 时使用纯文本。 20. Create a link that points to admin/change/password_token when using localhost:

        记住 URLhttp://localhost:8000 只适用于您的机器,这一点很重要。

        php $mail->Subject = 'Reset you account'; $mail->Body = "<p>To change your password please click <a href='http://localhost:8000/admin/change_password/$token'>this link</a></p>"; $mail->AltBody = "To change your password please go to this address: http://localhost:8000/admin/change_password/$token";

      18. 现在,一切都安排好了。发送电子邮件:

        php $mail->send();

      19. Create a session to inform the user and redirect the admin/reset:

        php Session::set('success', "Email sent to ".htmlentities($email)); Url::redirect('/admin/reset');

        完成的方法如下所示:

        有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

        ```php public function reset() { if (Session::get('logged_in')) { Url::redirect('/admin'); } ……. $title = 'Reset Account';

        $this->view->render('admin/auth/reset', compact('title', 'errors'));
        

        } ```

      20. When the user clicks on the link in the email, we need to handle the request. To do this, create another method called change_password that accepts a parameter called $token:

        此方法接受$token,将其传递给users模型中名为get_user_reset_token($token),的方法,并返回用户对象。如果令牌与数据库不匹配,则返回 null。

        有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

        php $user = $this->user->get_user_reset_token($token); if ($user == null) { $errors[] = 'user not found.'; }

        方法如下所示:

        有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

        ```php $title = 'Change Password';

        $this->view->render('admin/auth/change_password', compact('title', 'token', 'errors'));
        

        } ```

        render方法将$title$token,$errors传递给视图。

      21. Another view is needed. Create a view called change_password.php in app/views/admin/auth:

        有关完整的代码片段,请参阅代码文件文件夹中的Lesson 7.php文件。

        ```php <?php include(APPDIR.'views/layouts/header.php');?>

        ……