目录

工作流程

会话的工作流程很简单。当开始一个会话时,PHP 会尝试从请求中查找会话 ID (通常通过会话 cookie),如果请求中不包含会话 ID 信息,PHP 就会创建一个新的会话。会话开始之后,PHP 就会将会话中的数据设置到 $_SESSION 变量中。当 PHP 停止的时候,它会自动读取 $_SESSION 中的内容,并将其进行序列化,然后发送给会话保存管理器来进行保存。

默认情况下,PHP 使用内置的文件会话保存管理器(files)来完成会话的保存。也可以通过配置项 session.save_handler 来修改所要采用的会话保存管理器。对于文件会话保存管理器,会将会话数据保存到配置项 session.save_path 所指定的位置。

可以通过调用函数 session_start() 来手动开始一个会话。如果配置项 session.auto_start 设置为1,那么请求开始的时候,会话会自动开始。

PHP 脚本执行完毕之后,会话会自动关闭。同时,也可以通过调用函数 session_write_close() 来手动关闭会话。

会话id

一个访问者访问你的 web 网站将被分配一个唯一的 id, 就是所谓的会话 id. 这个 id 可以存储在用户端的一个 cookie 中,也可以通过 URL 进行传递。

有两种方式用来传送会话 ID:

  • Cookies
  • URL 参数

会话模块支持这两种方式。 Cookie 方式相对好一些,但是用户可能在浏览器中关闭 Cookie,所以第二种方案就是把会话 ID 直接并入到 URL 中,以保证会话 ID 的传送。

无需开发人员干预,PHP 就可以自动处理 URL 传送会话 ID 的场景。如果启用了 session.use_trans_sid 选项, PHP 将会自动在相对 URI 中包含会话 ID。

会话开始之后,可以使用 SID 常量。如果客户端未提供会话 cookie,该常量的展开形式为 session_name=session_id,反之,该常量为空字符串。因此,可以直接在 URL 中包含此常量的展开字符串而无需考虑会话 ID 的实际传送方式。

序列化

$_SESSION (和所有已注册得变量) 将被 PHP 使用内置的序列化方法在请求完成时进行序列化。

会话安全

安全重于业务,目前大部分场景都还是采用php的会话方式实现通信定制,对于安全,要重视再重视,先从会话安全开始,了解会话管理、会话问题、会话安全配置等:

PHP: 会话和安全 - Manual

  • 会话信息不安全,会话ID泄露。

    会话模块无法保证你存储在会话中的信息只能被创建会话的用户本人可见。你需要采取额外的手段来保护会话中的机密信息,至于采取何种方式来保护机密信息,取决于你在会话中存储的数据的机密程度。

    有很多种方式都可以导致会话 ID 被泄露给第三方。例如,JavaScript 注入,URL 中包含会话 ID,数据包侦听,或者直接访问你的物理设备等。如果会话 ID 被泄漏给第三方,那么他们就可以访问这个会话 ID 可以访问的全部资源。

  • 严格会话

    从 PHP 5.5.2 开始,新增加了一个配置项: session.use_strict_mode。当启用这个配置项,并且你所用的会话存储处理器支持的话,未经初始化的会话 ID 会被拒绝,并为其生成一个全新的会话,这可以避免攻击者使用一个已知的会话 ID 来进行攻击。

  • 会话与自动登录

    开发者不应该通过使用长生命周期的会话 ID 来实现自动登录功能,因为这种方式提高了会话被窃取的风险。开发者应该自己实现自动登录的机制。

  • CSRF跨站请求伪造攻击

    会话和认证无法避免跨站请求伪造攻击。开发者需要自己来实现保护应用不受 CSRF 攻击的功能。

    output_add_rewrite_var() 函数可以用来保护应用免受 CSRF 攻击。

    大部分 Web 应用框架都提供了 CSRF 保护的特性。详细信息请参考你所用的 Web 框架的文档。

    从 PHP 7.3 开始,对于会话 cookie 增加了 SameSite 属性,这个属性可以有效的降低 CSRF 攻击的风险。

会话文件锁

无论是通过调用函数 session_start() 手动开启会话,还是使用配置项 session.auto_start 自动开启会话,对于基于文件的会话数据保存(PHP 的默认行为)而言,在会话开始的时候都会给会话数据文件加锁,直到 PHP 脚本执行完毕或者显式调用 session_write_close() 来保存会话数据。在此期间,其他脚本不可以访问同一个会话数据文件。

对于大量使用 Ajax 或者并发请求的网站而言,这可能是一个严重的问题。解决这个问题最简单的做法是如果修改了会话中的变量,那么应该尽快调用 session_write_close() 来保存会话数据并释放文件锁。还有一种选择就是使用支持并发操作的会话保存管理器来替代文件会话保存管理器。

自定义会话管理器

如果需要在数据库中或者以其他方式存储会话数据,需要使用 session_set_save_handler() 函数来创建一系列用户级存储函数。 PHP 5.4.0 之后,你可以使用 SessionHandlerInterface 类或者通过继承 SessionHandler 类来扩展内置的管理器,从而达到自定义会话保存机制的目的。

函数 session_set_save_handler() 的参数即为在会话生命周期内要调用的一组回调函数: open, read, write 以及 close。还有一些回调函数被用来完成垃圾清理:destroy 用来删除会话, gc 用来进行周期性的垃圾收集。

因此,会话保存管理器对于 PHP 而言是必需的。默认情况下会使用内置的文件会话保存管理器。可以通过 session_set_save_handler() 函数来设置自定义会话保存管理器。一些 PHP 扩展也提供了内置的会话管理器,例如:sqlite, memcache 以及 memcached,可以通过配置项 session.save_handler 来使用它们。

会话管理器工作流程:

会话开始的时候,PHP 会调用 open 管理器,然后再调用 read 回调函数来读取内容,该回调函数返回已经经过编码的字符串。然后 PHP 会将这个字符串解码,并且产生一个数组对象,然后保存至 $_SESSION 超级全局变量。

当 PHP 关闭的时候(或者调用了 session_write_close() 之后), PHP 会对 $_SESSION 中的数据进行编码,然后和会话 ID 一起传送给 write 回调函数。 write 回调函数调用完毕之后,PHP 内部将调用 close 回调函数。

销毁会话时,PHP 会调用 destroy 回调函数。

根据会话生命周期时间的设置,PHP 会不时地调用 gc 回调函数。该函数会从持久化存储中删除超时的会话数据。超时是指会话最后一次访问时间距离当前时间超过了 $lifetime 所指定的值。

所以,php会话生命周期并不是实时结束的,可能过期了,但还能访问。

Session函数

  • session_start — 启动新会话或者重用现有会话

    session_start() 会创建新会话或者重用现有会话。如果通过 GET 或者 POST 方式,或者使用 cookie 提交了会话 ID,则会重用现有会话。

  • session_unset — 释放所有的会话变量

    session_unset() 会释放当前会话注册的所有会话变量。

    注意:请不要使用unset($_SESSION)来释放整个$_SESSION,因为它将会禁用通过全局$_SESSION去注册会话变量

    注意:session_unset仅从会话中删除变量-会话仍然存在。如果要销毁会话请使用session_destory

  • session_destroy — 销毁一个会话中的全部数据

    session_destroy() 销毁当前会话中的全部数据,但是不会重置当前会话所关联的全局变量,也不会重置会话 cookie。如果需要再次使用会话变量,必须重新调用 session_start() 函数。

    意味着:session_destory删除整个会话

    注意:删除当前用户对应的session文件以及释放sessionid,内存中的$_SESSION变量内容依然保留。

    所以彻底的销毁一个会话,我们建议:

    session_start();
    $_SESSION["name"] = "tsingchan";
    session_unset();
    session_destroy();
    var_dump($_SESSION);
    var_dump($_SESSION['name']); //如果没有session_unset,将会继续输出tsingchan,如果没有session_detory,那么会话仍然存在,只是name不存在了
    
  • session_save_path — 读取/设置当前会话的保存路径

    指定会话数据保存的路径。必须在调用 session_start() 函数之前调用 session_save_path() 函数。

  • session_name — 读取/设置会话名称

    用在 cookie 或者 URL 中的会话名称,例如:PHPSESSID。只能使用字母和数字作为会话名称,建议尽可能的短一些,并且是望文知意的名字(对于启用了 cookie 警告的用户来说,方便其判断是否要允许此 cookie)。如果指定了 name 参数,那么当前会话也会使用指定值作为名称。

    请求开始的时候,会话名称会被重置并且存储到 session.name 配置项。因此,要想设置会话名称,那么对于每个请求,都需要在调用 session_start() 函数之前调用 session_name() 函数。

  • session_id — 获取/设置当前会话 ID

    如果指定了 id 参数的值,则使用指定值作为会话 ID。必须在调用 session_start() 函数之前调用 session_id() 函数。不同的会话管理器对于会话 ID 中可以使用的字符有不同的限制。例如文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 , (逗号)和 - (减号)

  • session_regenerate_id — 使用新生成的会话 ID 更新现有会话 ID

    在不修改当前会话中数据的前提下使用新的 ID 替换原有会话 ID。

    session_regenerate_id([ bool $delete_old_session = FALSE] ) : bool
    

    参数$delete_old_session表示是否删除原 ID 所关联的会话存储文件。如果你需要避免会话并发访问冲突,那么不应该立即删除会话中的数据。如果你需要防止会话劫持攻击,那么可以立即删除会话数据。

    session_start();
    $_SESSION["name"] = "tsingchan";
    
    echo session_id();
    echo "<br />";
    session_regenerate_id(true);//为当前会话重新生成会话id,下一次访问将自动更换会话id(前后端)
    echo session_id();
    echo "<br />";
    var_dump($_SESSION);
    var_dump($_SESSION['name']);
    //session_unset();
    //session_destroy();
    
  • session_write_close — Write session data and end session

    结束当前会话并存储会话数据。

    我们可以手动结束会话。

    会话数据通常在脚本终止后存储,而不需要调用session_write_close(),但是由于会话数据被锁定以防止并发写,所以在任何时候只有一个脚本可以对会话进行操作。

  • session_set_save_handler — 设置用户自定义会话存储函数

    如果需要在数据库中或者以其他方式存储会话数据,需要使用 session_set_save_handler() 函数来创建一系列用户级存储函数。 PHP 5.4.0 之后,你可以使用 SessionHandlerInterface 类或者通过继承 SessionHandler 类来扩展内置的管理器,从而达到自定义会话保存机制的目的。

    函数 session_set_save_handler() 的参数即为在会话生命周期内要调用的一组回调函数: open, read, write 以及 close。还有一些回调函数被用来完成垃圾清理:destroy 用来删除会话, gc 用来进行周期性的垃圾收集。

    session_set_save_handler有两种传参实现方式:

    session_set_save_handler( callable $open, callable $close, callable $read, callable $write, callable $destroy, callable $gc[, callable $create_sid[, callable $validate_sid[, callable $update_timestamp]]] ) : bool
    

    自 PHP 5.4 开始,可以使用下面的方式来注册自定义会话存储函数:

    session_set_save_handler( object $sessionhandler[, bool $register_shutdown = TRUE] ) : bool
    

    $sessionhandler实现了 SessionHandlerInterface, SessionIdInterface 和/或 SessionUpdateTimestampHandlerInterface 接口的对象,例如 SessionHandler。自 PHP 5.4 之后可以使用。

    $register_shutdown将函数 session_write_close() 注册为 register_shutdown_function() 函数。

  • session_abort — Discard session array changes and finish session session_abort()在不保存数据的情况下完成会话。这样就保留了会话数据中的原始值。

  • session_cache_expire — 返回当前缓存的到期时间

  • session_cache_limiter — 读取/设置缓存限制器

  • session_commit — session_write_close 的别名

  • session_create_id — Create new session id

    用于为当前会话创建newsession id。它返回无冲突的会话id。

  • session_decode — 解码会话数据

  • session_encode — 将当前会话数据编码为一个字符串

  • session_gc — Perform session data garbage collection

  • session_get_cookie_params — 获取会话 cookie 参数

  • session_is_registered — 检查变量是否在会话中已经注册

  • session_module_name — 获取/设置会话模块名称

  • session_register_shutdown — 关闭会话

  • session_register — Register one or more global variables with the current session

  • session_reset — Re-initialize session array with original values

  • session_set_cookie_params — 设置会话 cookie 参数

  • session_status — 返回当前会话状态

    • PHP_SESSION_DISABLED 会话是被禁用的。
    • PHP_SESSION_NONE 会话是启用的,但不存在当前会话。
    • PHP_SESSION_ACTIVE 会话是启用的,而且存在当前会话。
  • session_unregister — Unregister a global variable from the current session