在 PHP 中使用和管理 Session 并实现简单的用户登录功能


Session vs. Cookie

与 Cookie 一样,Session 技术也是用于解决 HTTP 协议无状态的问题,不过,与 Cookie 数据保存在客户端不同,Session 数据存储在服务端,然后通过分配一个全局唯一的 ID 与特定用户关联(通常在用户认证通过后分配),但 Session 又与 Cookie 紧密关联,因为这个 Session ID 通常会存储到 Cookie 中,在其生命周期内,用户发起请求时就会带上它,这样服务端通过解析存储在 Cookie 中的 Session ID 就能识别特定的客户端用户,并返回与之关联的 Session 数据,比如前面提到的电商网站中的购物车数据。

注:虽然 Session ID 也可以包含在 URL 请求参数(查询字符串)中,但是维护成本太高,不如直接存储到 Cookie 中方便,所以渐渐废弃,现在基本上都是通过 Cookie 存储,并以此建立服务端与客户端的关联和用户认证状态的维护。

Session 运行原理和配置浅析

Session 技术的一个主要用途就是用户认证,其基本实现原理是在用户登录成功后为其生成一个全局唯一的 Session ID,并且将必要的用户会话数据存储到服务端对应的 Session 数据中(后续可通过 Session ID 查询),然后通过 Set-Cookie 响应头将 Session ID 发送到客户端,并存储到客户端 Cookie,过期时间与服务端维护的 Session 有效期一致(默认是 3 小时),在 Session 有效期内,所有客户端请求都会自动通过 Cookie 请求头带上这个 Session ID,服务端解析到这个 Session ID 并且查询对应 Session 数据存在,则表明该客户端用户是一个已认证用户,进而返回对应的用户信息该客户端,让客户端可以标识对应用户的登录状态。

注:除了用户认证之外,Session 还有一些其他的使用场景,比如验证码校验、存储一些其他的用户状态信息(比如限定访问频率、禁止重复提交表单等)。

另外,在服务端,Session 数据默认存储在文件中,这是通过 php.inisession.save_handler 配置项配置的:

-w791

你还可以通过 session.save_path 指定存储 Session 数据文件的路径(默认是 /tmp,该配置仅在 session.save_handlers 值为 files 时有效),另外,在上述代码中,还可以看到与 Cookie 相关的配置:

-w447

当我们基于 Cookie 存储 Session ID 时,会通过这个配置项设置对应的 Cookie 属性,通过下面两个配置可以看到默认就是基于 Cookie 保存 Session ID,并且只使用 Cookie 保存(1 表示 true):

-w476

并且这个存储在 Cookie 中的 Session ID 对应名称是 PHPSESSID

session.name => PHPSESSID => PHPSESSID

最后,Session 功能默认是关闭的,如果要使用 Session 需要主动开启,Session 默认的有效期是 3 个小时(通过 cache_expire 配置项配置,单位为分钟),3 小时后自动销毁:

-w495

当然,以上配置在运行时都是支持动态设置的。

Session 的基本使用

我们以用户认证为例,演示下如何在 PHP 中使用 Session。

基本增删改查演示

首先我们来简单看下如何启用并设置/读取 Session 数据,在 php_learning/http 目录下新建 session.php

<?php
session_save_path('./session');
session_start();
if (isset($_SESSION['name'])) {
    echo $_SESSION['name'];
    exit();
}
$_SESSION['name'] = '学院君';

我们调用 PHP 内置的 session_save_path 函数设置 Session 存储目录为当前目录下的 session 子目录中,然后调用 session_start 函数启动 Session(这一行代码最为关键,不能省略,否则无法使用 Session 保存和读取数据)。

接下来,我们就可以通过 PHP 超全局变量 $_SESSION 读取和设置 Session 数据了,和之前的其他超全局变量一样,$_SESSION 也是一个关联数组,通过关联数组的方式更新或者新增 Session 数据即可,如果要删除某个 Session 数据,使用 unset 函数即可:

unset($_SESSION['name']);

我们在浏览器中访问 http://localhost:9000/session.php,首次访问没有任何输出,因为 Session 中还没有数据,但是再次刷新就可以看到 Session 中的数据被打印出来:

-w722

此时看 http/session 目录下,已经包含生成的 Session 数据文件了:

-w830

并且在浏览器中,也可因看到 Cookie 中包含了对应的 PHPSESSID Cookie:

-w568

这个就是服务端自动生成并发送到客户端 Cookie 的 Session ID,这些逻辑都有 PHP 底层自动维护,我们不必关心。

实现简单的用户登录功能

接下来,我们来基于 Session 在 PHP 代码中实现简单的用户认证。

登录页面

首先将 http/form.html 重命名为 form.php,并加入错误提示(如果有的话),将表单提交链接调整为 login.php

<!DOCTYPE html>
<html>
...
<body>
<div class="container col-4">
    <h1 class="text-center">登录表单</h1>
    <?php if (!empty($error)): ?>
        <div class="alert alert-danger" role="alert">
            <?= $error ?>
        </div>
    <?php endif; ?>
    <form method="post" action="login.php">
        <!-- name form input -->
        <div class="form-group">
            <label for="name">用户名</label>
            <input type="text" class="form-control" name="name" id="name">
        </div>
        <div class="form-group">
            <label for="password">密码</label>
            <input type="password" class="form-control" id="password" name="password">
        </div>
        <button type="submit" class="btn btn-primary">登录</button>
    </form>
</div>
</body>
</html>

用户登录处理

然后我们在 http 目录下新建一个 login.php 编写用户登录处理代码:

<?php
session_save_path('./session');
session_start();

// 模拟数据库数据
$data = [
    [
        'id'  => 1,
        'name' => '测试账号',
        'password' => '123456'
    ],
    [
        'id'  => 2,
        'name' => '学院君',
        'password' => '123456'
    ]
];

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $name = $_POST['name'];
    $password = $_POST['password'];
    if (empty($name) || empty($password)) {
        $error = '用户名/密码不能为空,请重试';
    } else {
        // 简单模拟用户名和密码是否匹配
        $user = array_filter($data, function ($item) use ($name, $password) {
            if ($item['name'] == $name && $item['password'] == $password) {
                return true;
            }
            return false;
        });
        if (!empty($user)) {
            $_SESSION['user'] = array_shift($user);
            header('Location: /user.php');
        } else {
            $error = '用户名/密码不正确,请重试';
        }
    }
}

include_once 'form.php';

开头两行代码还是 Session 初始化设置和启动,然后通过数组模拟数据库用户数据,接下来,如果是 GET 请求,直接通过 include_once 'form.php' 渲染登录表单页面,如果是 POST 请求,则通过 $_POST 读取表单输入,如果用户名密码为空或者在「数据库」中不存在,则返回对应的错误提示信息,让用户重新输入并提交表单,否则在 $_SESSION['user'] 中设置对应的用户信息,并将用户重定向到 user.php 页面显示登录用户信息。

显示用户信息

最后,我们在 http 下新建 user.php,然后在其中编写登录用户信息展示代码:

<?php
session_save_path('./session');
session_start();

if (isset($_SESSION['user'])) {
    header('Content-Type: application/json');
    $user = $_SESSION['user'];
    echo json_encode($user);
} else {
    header('HTTP/1.1 401 Unauthorized');
    echo '登录后才能访问: <a href="login.php">立即登录</a>';
}

开始还是要初始化和启动 Session(这块代码可以独立出去,然后通过 require 引入,从而方便代码复用),这里为了简化业务逻辑,直接返回包含用户信息的 JSON 数据,如果用户 Session 数据为空,表示该用户尚未登录,返回 401 响应,并提示用户点击登录链接登录。

注:这里,读取 Cookie 中的 Session ID 并与服务端对应的 Session 数据映射由 PHP 系统底层完成,上层业务开发人员不需要关心其细节。

测试用户登录功能

现在,我们访问 http://localhost:9000/user.php,会提示我们需要登录:

-w594

点击登录链接,即可进入登录页面,如果输入的登录账号和密码不正确,会提示重新输入:

-w677

如果登录成功,则会跳转到 http://localhost:9000/user.php 返回登录用户信息:

-w618

感兴趣的同学可以自行实现对应的用户退出逻辑(清除保存用户登录信息的 Session 即可)。

小结

至此,我们就完成了 PHP 中 Web 开发(HTTP 编程)相关的所有基础知识的介绍,包括服务器、请求处理、HTTP响应、Cookie 和 Session,再结合之间的 PHP 基础知识、面向对象编程、数据库操作,我们就已经可以开发出常见的 Web 应用了,从下篇教程开始,我们将结合上述基础知识编写一个简单 PHP Web 框架(MVC 模式),有了这个过渡,相信大家再去看 Laravel 的设计和实现就更加得心应手了。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 如何在 PHP 中使用和管理 Cookie

>> 下一篇: PHP 命名空间与类自动加载实现