怎样在 Ubuntu 20.04 上使用 Redis 管理 PHP 会话数据

介绍

当用户与您的 Web 应用程序交互时,他们的当前状态称为会话。 会话数据允许您的应用程序在最终用户登录的整个期间记住他们的身份。在典型的 Web 应用程序中,最终用户在登录表单中提交用户名和密码。 然后,您的应用程序会在数据库中找到这些凭据。 如果匹配,您只需授予用户访问您的 Web 应用程序的权限。 否则,用户会收到拒绝访问错误。

由于登录用户可以从您的 Web 应用程序请求不同的页面,因此您必须找到一种方法来持久保存会话数据。 这有助于用户在会话的生命周期内轻松浏览您的站点或 Web 应用程序,而无需重新提交其登录凭据。 在 PHP 中,实现此功能的最佳方法是向成功登录到您的应用程序的任何用户颁发访问令牌。 然后,您应该将令牌保存在数据库表中,并以 HTTP cookie 的形式将其发送回用户的浏览器。

当最终用户的浏览器接收到 cookie 数据时,它将在任何后续 HTTP 请求期间将其发回。 从现在开始,您可以验证数据库中的访问令牌,以便在每次请求网页时记住用户的详细信息。

颁发和验证访问令牌的整个过程需要您的应用程序从数据库中写入和读取大量数据。 这可能会损害用户体验,因为每个 HTTP 请求都需要往返数据库以验证访问令牌。 为了克服这一挑战并提供快速响应,您应该将会话数据缓存在内存数据库(如 Redis)中。 在本指南中,您将学习如何在 Ubuntu 20.04 上使用 Redis 服务器管理 PHP 会话数据。

先决条件

要完成本教程,您需要具备以下条件:

高性能 Ubuntu 20.04 服务器。 一个 sudo 用户。 灯组。 一个Redis服务器

1 – 为 PHP 安装 Redis 扩展

要与 PHP 中的 Redis 服务器键值存储进行通信,您需要安装 php-redis 图书馆。 首先,通过 SSH 连接到您的服务器并更新包信息索引。

$ sudo apt update

接下来,发出以下命令来安装 php-redis 延期。

$ sudo apt install -y php-redis

重新启动 Apache 网络服务器以加载新更改。

$ sudo systemctl restart apache2

在 PHP 上初始化用于与 Redis 服务器交互的 API 后,您将在下一步中创建一个测试数据库和一个表。

2 – 创建测试数据库和表

在此步骤中,您将设置一个示例数据库和一个表来存储用户的登录凭据,包括他们的姓名和散列密码。 以 root 身份登录到您的 MySQL 服务器。

$ sudo mysql -u root -p

出现提示时输入 MySQL 服务器的密码,然后按 ENTER 继续。 接下来,发出 CREATE DATABASE 命令设置一个新的 sample_cms 数据库。

mysql> CREATE DATABASE sample_cms;

确认下面的输出以确保您已创建数据库。

Query OK, 1 row affected (0.01 sec)

接下来,您将为您的用户创建一个非 root 用户 sample_cms 数据库,因为不建议在 PHP 脚本中使用 root 凭据。 运行下面的命令来创建一个 sample_cms_user 并替换 EXAMPLE_PASSWORD 使用强密码。

mysql> CREATE USER 'sample_cms_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'EXAMPLE_PASSWORD';
       GRANT ALL PRIVILEGES ON sample_cms.* TO 'sample_cms_user'@'localhost';           
       FLUSH PRIVILEGES;

确保您收到以下回复以确认您已成功创建 sample_cms_user 用户。

...

Query OK, 0 rows affected (0.00 sec)

接下来,运行 USE 命令切换到新的 sample_cms 数据库。

mysql> USE sample_cms;

通过验证下面的输出,确保您选择了新数据库。

Database changed

接下来,设置一个 system_users 桌子。 您将使用 user_id 列以唯一标识每个用户的帐户。 然后,使用 AUTO_INCREMENT 此字段上的关键字以自动生成新的 user_id 对于每条记录。 要在表上容纳大量用户,请使用 BIGINT 上的数据类型 user_id 柱子。 最后,使用 VARCHAR 的数据类型 username, first_name, last_name, 和 pwd 领域。 关键字 ENGINE = InnoDB 允许您使用 InnoDB 引擎是快速和事务就绪的。

要创建 system_users 表,运行以下命令。

mysql> CREATE TABLE system_users (
           user_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
           username VARCHAR(15),
           first_name VARCHAR(50),
           last_name VARCHAR(50),
           pwd VARCHAR(255)
       ) ENGINE = InnoDB;

通过确认下面的输出,确保您已成功创建表。

Query OK, 0 rows affected (0.02 sec)

从 MySQL 命令行界面退出。

mysql> QUIT;

输出。

Bye

system_users 表现在已准备好接收数据。 在下一步中,您将创建一个用于填充表的 PHP 脚本。

3 – 创建注册脚本

要测试用户会话功能,您需要一些示例记录。 在这一步中,您将设置一个 PHP 脚本来接受来自 Linux 的用户数据 curl 命令并依次填充 system_users 桌子。 在生产环境中,您可以创建一个注册页面,用户可以在其中注册您的应用程序。 对于本指南,您只需要一个脚本来自动化用户的注册过程,而无需创建任何注册表单。

开一个新的 /var/www/html/register.php Web 服务器根目录下的文件。

$ sudo nano /var/www/html/register.php

接下来,在文件中输入以下信息。

<?php 

    try {
            $db_name="sample_cms";
            $db_user="sample_cms_user";
            $db_password = 'EXAMPLE_PASSWORD';
            $db_host="localhost";

            $pdo = new PDO('mysql:host=" . $db_host . "; dbname=" . $db_name, $db_user, $db_password);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
            $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 

            $sql = "insert into system_users
                    (
                    username,
                    first_name,
                    last_name,
                    pwd
                    )
                    values
                    (
                    :username,
                    :first_name,
                    :last_name,
                    :pwd
                    )                       
                    '; 

            $data = [];

            $data = [
                    'username'   => $_POST['username'],
                    'first_name' => $_POST['first_name'],
                    'last_name'  => $_POST['last_name'],
                    'pwd'        => password_hash($_POST['pwd'], PASSWORD_BCRYPT)             
                    ];

            $stmt = $pdo->prepare($sql);
            $stmt->execute($data); 

            echo "User data saved successfully.n" ;

        } catch (PDOException $e) {
            echo 'Database error. ' . $e->getMessage();
        }

完成编辑后保存并关闭文件。 在文件的顶部,您声明了您在其中设置的数据库变量 第2步. 下一个。 您正在使用 PHP PDO 库执行准备好的语句以将数据保存到 system_users 桌子。 为避免以纯文本格式保存密码,您使用了以下语句 password_hash($_POST['pwd'], PASSWORD_BCRYPT) 使用 bcrypt 算法散列密码。

接下来,执行以下命令 curl 命令在您的数据库中创建一些用户的帐户。 请注意,您可以通过替换为密码使用更强的值 ...EXAMPLE_PASSWORD_1..., ...EXAMPLE_PASSWORD_2..., 和 ...EXAMPLE_PASSWORD_3... 使用您想要的密码。

$ curl --data "username=john_doe&first_name=JOHN&last_name=DOE&pwd=EXAMPLE_PASSWORD_1" http://localhost/register.php
$ curl --data "username=mary_smith&first_name=MARY&last_name=SMITH&pwd=EXAMPLE_PASSWORD_2" http://localhost/register.php
$ curl --data "username=roe_jane&first_name=ROE&last_name=JANE&pwd=EXAMPLE_PASSWORD_3" http://localhost/register.php

执行每个命令后,您应该得到以下输出以确认您已成功创建用户帐户。

...
User data saved successfully.

接下来,通过以身份登录 MySQL 服务器来确认记录 sample_cms_user. 你不需要任何 sudo 执行以下命令的权限。

$ mysql -u sample_cms_user -p

输入密码 sample_cms_user(例如, EXAMPLE_PASSWORD) 并按 ENTER 继续。 然后切换到新的 sample_cms 数据库。

mysql> USE sample_cms;

通过确认下面的输出,确保您已切换到数据库。

Database changed

接下来,运行一个 SELECT 声明反对 system_users 表来验证记录。

mysql> SELECT
       user_id,
       username,
       first_name,
       last_name,
       pwd 
       FROM system_users;

您现在应该会收到以下输出以确认数据已就位。 如您所见, pwd 列被散列。

+---------+------------+------------+-----------+--------------------------------------------------------------+
| user_id | username   | first_name | last_name | pwd                                                          |
+---------+------------+------------+-----------+--------------------------------------------------------------+
|       1 | john_doe   | JOHN       | DOE       | $2y$10$8WcrxHkCUuRM4upVmYJhe.xKAXpoQkVQahoYI87RAlgSeTaxgq3Km |
|       2 | mary_smith | MARY       | SMITH     | $2y$10$Yk3ZngColV9WGL4c/mgxvuwaVMutq73NW1mWXMrydoukEUxpq0XA2 |
|       3 | roe_jane   | ROE        | JANE      | $2y$10$TcSaOC6MylunFXI4s.XTW.W70i9XjJIa3VyT2JXBygW4pvSoKvj4y |
+---------+------------+------------+-----------+--------------------------------------------------------------+
3 rows in set (0.01 sec)

从 MySQL 服务器命令行界面退出。

mysql> QUIT;

输出。

Bye

使用示例用户帐户后,您现在将在下一步中创建一个 PHP 登录页面。

4 – 创建用户登录表单和处理脚本

使用您的示例应用程序的访问者将通过登录页面访问它。 在此步骤中,您将创建一个接受用户名和密码的 HTML 表单。 然后,此表单会将登录凭据发送到 PHP 脚本,该脚本会比较您创建的数据库中的值,以便在存在匹配记录的情况下对用户进行身份验证。

开一个新的 /var/www/html/login.php 文件。

$ sudo nano /var/www/html/login.php

然后将以下信息输入到 /var/www/html/login.php 文件。

<html>
  <head>
    <title>User Login</title>
  </head>
  <body>
    <h2>User Login Page</h2> 
    <form action="/process.php" method="post">
      <label for="username">Username:</label><br>
      <input type="text" id="username" name="username" ><br><br>
      <label for="pwd">Password:</label><br>
      <input type="password" id="pwd" name="pwd"><br><br>
      <input type="submit" value="Submit">
    </form>         
  </body>
</html>

保存并关闭文件。 该声明 action="/process.php 指示表单将数据发送到名为 process.php. 接下来,使用 nano 创建新的 /var/www/html/process.php 文件。

$ sudo nano /var/www/html/process.php

然后,将下面的信息输入到 /var/www/html/process.php 文件。

<?php 

    try {
            $db_name="sample_cms";
            $db_user="sample_cms_user";
            $db_password = 'EXAMPLE_PASSWORD';
            $db_host="localhost";

            $pdo = new PDO('mysql:host=" . $db_host . "; dbname=" . $db_name, $db_user, $db_password);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
            $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 

            $sql = "select
                    user_id,
                    username,
                    first_name,
                    last_name,
                    pwd                                 
                    from system_users
                    where username = :username                        
                    '; 

            $data = [];
            $data = [
                    'username' => $_POST['username']                        
                    ];

            $stmt = $pdo->prepare($sql);
            $stmt->execute($data);               

            $user_data = [];

            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {         
                $user_data = $row;          
            }                 

            if (password_verify($_POST['pwd'], $user_data['pwd']) == true) {
                $session_token      = bin2hex(openssl_random_pseudo_bytes(16));
                $user_data['token'] = $session_token;

                setcookie('token', $session_token, time()+3600);
                setcookie('username', $user_data['username'], time()+3600);

                $redis = new Redis(); 
                $redis->connect('127.0.0.1', 6379);

                $redis_key =  $user_data['username'];

                $redis->set($redis_key, serialize($user_data)); 
                $redis->expire($redis_key, 3600);                  

                header('Location: dashboard.php');
            } else {
                header('Location: login.php');
            }               

        } catch (PDOException $e) {
            echo 'Database error. ' . $e->getMessage();
        }

保存并关闭文件。 在上面的文件中,您正在连接到 sample_cms 数据库,那么你正在寻找一个 username 基于从收到的价值 $_POST['username'] 变量来自 login.php 形式。 如果匹配,则将用户信息放入名为的数组中 $user_data. 接下来,您将使用 PHP if (password_verify($_POST['pwd'], $user_data['pwd']) == true) {...} 检查提供的密码是否与密码中的值匹配的语句 system_users 桌子。

如果用户输入了正确的密码,您将使用以下语句为用户分配一个新的会话令牌 $session_token = bin2hex(openssl_random_pseudo_bytes(16));. 接下来,您将在用户的浏览器中创建两个过期时间为 3600 秒(1 小时)。 第一个 cookie 包含访问令牌的值 ($session_token) 并且第二个 cookie 存储 username. 下次用户向您的 Web 应用程序发出请求时,您将使用这些详细信息来识别用户,而无需他们再次登录。

然后,您将使用以下代码块打开 Redis 服务器的新实例。

...
$redis = new Redis(); 
$redis->connect('127.0.0.1', 6379);
...

由于多个用户可能会连接到您的应用程序,因此您可以通过命名 Redis 键来区分他们的会话数据($redis_key) 与 username 值使用 $redis->set$redis->expire 职能。 这些函数将用户的会话数据缓存在 Redis 服务器中,以便更快地检索,而不是将数据保存到 MySQL 数据库中。

...
$redis_key =  $user_data['username'];

$redis->set($redis_key, serialize($user_data)); 
$redis->expire($redis_key, 3600);      
...

最后,一旦您对用户进行了身份验证并将会话处理传递给 Redis 服务器,您就会将用户定向到 dashboard.php 页面使用 header('Location: dashboard.php'); 声明,否则,您将使用无效登录凭据的未经身份验证的用户回退到 login.php 带有以下语句的页面。

...
    header('Location: dashboard.php');
} else {
   header('Location: login.php');
}         
...

login.phpprocess.php 页面现在已准备好对用户进行身份验证。 在下一步中,您将创建一个仪表板页面,登录用户将被重定向到该页面。

5 – 创建仪表板页面

在 Web 应用程序中,仪表板是允许用户浏览菜单和链接的主页面。 它只能由在输入有效凭据后从登录页面重定向的经过身份验证的用户访问。

由于任何人都可能尝试通过在浏览器上输入其 URL 来直接访问仪表板页面,因此授权访问此页面的唯一方法是检查来自用户浏览器 cookie 的会话数据。

在上一步中,您为经过身份验证的用户分配了两个唯一的 cookie,并将它们缓存在 Redis 服务器中。 在此步骤中,您将检查这些 cookie 的值并从 Redis 服务器重新验证它们以确保它们有效。

任何用户访问 dashboard.php 没有任何 cookie 将收到 Access denied. 错误。 此外,如果 cookie 信息已被修改并且与 Redis 服务器中的值不匹配,您的脚本应以 Invalid token. 错误。

使用 nano 文本编辑器打开一个新的 /var/www/html/dashboard.php 文件。

$ sudo nano /var/www/html/dashboard.php

然后在文件中输入以下信息。

<html>
    <head>
      <title>Dashboard</title>
    </head>
    <body>
      <h1>Dashboard</h1>
      <p>

        <?php 

            $redis = new Redis(); 
            $redis->connect('127.0.0.1', 6379);

            if ($redis->exists($_COOKIE['username'])) {

                $user_data = unserialize($redis->get($_COOKIE['username']));                    

                if ($_COOKIE['token'] == $user_data['token']) {                 
                    echo "Welcome, " . $user_data['first_name'] . ' ' . $user_data['last_name'] . "<br>"
                         . "Your token is " . $user_data['token']; 
                } else {
                    echo "Invalid token.";
                }

            } else {
                  echo "Access denied.";
            }                         
        ?>

      </p>
  </body>
</html>

完成编辑后保存并关闭文件。 在上面的文件中,您正在连接到 Redis 服务器,然后,您正在使用该语句 ...if ($redis->exists($_COOKIE['username'])) {...}... 检查是否有以 $_COOKIE['username'] 值,否则,您将使用 Access denied. 错误。 接下来,如果 Redis 缓存了具有该名称的键,您将其值与浏览器的访问令牌 cookie ($_COOKIE['token'])。 如果值相同,则您使用以下语句从 Redis 服务器回显用户的详细信息:

echo "Welcome, " . $user_data['first_name'] . ' ' . $user_data['last_name'] . "<br>"
     . "Your token is " . $user_data['token']; 

请注意,您应该从这里包含会话处理逻辑 dashboard.php 要求您了解用户登录状态的所有页面上的页面。 在下一步中,您将测试您编写的所有代码的功能。

6 – 测试应用程序

在浏览器上,访问下面的页面并替换 192.0.2.1 使用您的网络服务器的正确公共 IP 地址或域名。

http://192.0.2.1/login.php

系统会提示您输入用户名和密码。 进入 JOHN DOE's 凭据(例如, 用户名:john_doe, 密码:EXAMPLE_PASSWORD_1) 并单击 提交 登录。

您现在应该被重定向到 http://192.0.2.1/dashboard.php 页面并收到欢迎信息。

即使你刷新了 dashboard.php 页面,您仍然会登录,因为您将会话数据设置为持续一段时间 1小时. 上面的截图证实 Redis 能够在不访问 MySQL 数据库的情况下处理您的会话数据,这将大大提高您的 Web 应用程序的响应时间,特别是如果您有很多用户。

结论

在本指南中,您已经设置了一个示例数据库和一个用户表。 然后,您编写了一个登录页面,该页面将用户的帐户凭据发送到 PHP 页面,该页面将数据保存到 Redis 服务器以管理会话。 使用本指南中的逻辑设计一个需要可扩展用户会话管理的高可用项目,

除了处理用户的会话数据,Redis 服务器还提供了更多的功能。 请参阅以下文档以了解如何使用 Redis 服务器进一步减少数据库和前端负载:

在 Ubuntu 20.04 上使用 Redis 和 PHP 缓存 MySQL 数据 在 Ubuntu 20.04 上使用 PHP 实现 Redis 队列和工作线程 在 Ubuntu 20.04 上使用 Redis 作为 PHP 的速率限制器

注:本教程在Vultr VPS上测试通过,如需部署请前往Vultr.com