buimatic/session

基于 aura/session

2.0.1 2015-03-27 18:33 UTC

This package is not auto-updated.

Last update: 2024-10-02 09:36:35 UTC


README

提供会话管理功能,包括懒加载会话启动、会话段、仅下次请求(“闪光”)值和CSRF工具。

前言

安装

此库需要PHP 5.3或更高版本;我们原则上建议使用最新版本的PHP。它没有用户空间依赖。

您可以通过Composer安装和自动加载,地址为 aura/session

或者,您可以 下载一个发布版 或克隆此存储库,然后要求或包含其 autoload.php 文件。

质量

Scrutinizer Code Quality Code Coverage Build Status

要在命令行运行单元测试,请执行 composer install,然后在包根目录下执行 phpunit。这需要 Composercomposer 的形式可用,以及 PHPUnitphpunit 的形式可用。

此库试图遵守 PSR-1PSR-2PSR-4。如果您注意到遵守上的疏忽,请通过拉取请求发送补丁。

社区

要提问、提供反馈或与Aura社区进行其他沟通,请加入我们的 Google Group,关注 @auraphp on Twitter 或在Freenode上的 #auraphp 上与我们聊天。

入门

实例化

开始的最简单方法是使用 SessionFactory 创建一个 Session 管理对象。

<?php
$session_factory = new \Aura\Session\SessionFactory;
$session = $session_factory->newInstance($_COOKIE);
?>

然后我们可以使用 Session 实例创建 Segment 对象来管理会话值和闪存。 (通常,我们不需要直接操作 Session 管理器 - 我们将主要与 Segment 对象一起工作。)

在正常的PHP中,我们将会话值保存在 $_SESSION 数组中。然而,当不同的库和项目尝试修改相同的键时,产生的冲突可能导致意外的行为。为了解决这个问题,我们使用 Segment 对象。每个 Segment 针对在 $_SESSION 数组中的命名键进行去冲突。

例如,如果我们为 Vendor\Package\ClassName 获取一个 Segment,那么该 Segment 将包含对 $_SESSION['Vendor\Package\ClassName'] 的引用。然后我们可以在 Segmentset()get() 值,并且值将驻留在该引用下的数组中。

<?php
// get a _Segment_ object
$segment = $session->getSegment('Vendor\Package\ClassName');

// try to get a value from the segment;
// if it does not exist, return an alternative value
echo $segment->get('foo'); // null
echo $segment->get('baz', 'not set'); // 'not set'

// set some values on the segment
$segment->set('foo', 'bar');
$segment->set('baz', 'dib');

// the $_SESSION array is now:
// $_SESSION = array(
//      'Vendor\Package\ClassName' => array(
//          'foo' => 'bar',
//          'baz' => 'dib',
//      ),
// );

// try again to get a value from the segment
echo $segment->get('foo'); // 'bar'

// because the segment is a reference to $_SESSION, we can modify
// the superglobal directly and the segment values will also change
$_SESSION['Vendor\Package\ClassName']['zim'] = 'gir'
echo $segment->get('zim'); // 'gir'
?>

会话段的好处是我们可以通过使用类名(或某些其他唯一名称)作为段名称来去冲突 $_SESSION 超全局变量中的键。使用段,不同的包可以在不互相干扰的情况下使用 $_SESSION 超全局变量。

要清除 Segment 上的所有值,请使用 clear() 方法。

闪光值

值会持续到会话被清除或销毁。然而,有时设置一个只通过下一个请求传播并在之后丢弃的值是有用的。这些被称为“闪存”值。

设置和获取闪存值

要在上设置闪存值,请使用setFlash()方法。

<?php
$segment = $session->getSegment('Vendor\Package\ClassName');
$segment->setFlash('message', 'Hello world!');
?>

然后,在后续请求中,我们可以使用getFlash()读取闪存值。

<?php
$segment = $session->getSegment('Vendor\Package\ClassName');
$message = $segment->getFlash('message'); // 'Hello world!'
?>

注意:与get()一样,如果闪存键不存在,我们可以提供一个备选值。例如,getFlash('foo', 'not set')如果不存在'foo'键,将返回'not set'。

使用setFlash()只使闪存值在下一个请求中可用,而不是当前请求。要使闪存值在当前请求和下一个请求中都立即可用,请使用setFlashNow($key, $val)

使用getFlash()仅返回从前一个请求设置的当前可用的值。要读取将在下一个请求中可用的值,请使用getFlashNext($key, $alt)

保留和清除闪存值

有时我们希望将闪存值保留在当前请求中以便用于下一个请求。我们可以通过调用SegmentkeepFlash()方法按段进行,或者通过调用SessionkeepFlash()方法保留所有段的所有闪存。

同样,我们可以按段或会话范围清除闪存值。在Segment上使用clearFlash()方法清除仅针对该段的闪存,或在Session上使用相同的方法清除所有段的所有闪存值。

延迟会话启动

仅实例化Session管理器并从中获取Segment并不会调用session_start()。相反,session_start()只在某些情况下发生。

  • 如果我们从Segment中读取(例如使用get()),Session会检查是否已经设置了会话cookie。如果是这样,它将调用session_start()来恢复先前启动的会话。如果没有,它知道没有先前存在的$_SESSION值,因此不会调用session_start()

  • 如果我们向Segment写入(例如使用set()),Session将始终调用session_start()。这将恢复现有会话,如果存在的话,或者如果没有,将启动一个新的会话。

这意味着我们可以随意创建每个Segment,而session_start()将不会在实际上以特定方式与Segment交互之前被调用。这有助于节省启动会话所需的资源。

当然,我们可以通过调用Sessionstart()方法强制会话启动或重新激活,但这会违背延迟加载会话的目的。

保存、清除和销毁会话

注意:这些方法适用于所有段的所有会话数据。

要保存会话数据并在当前请求中结束其使用,请调用Session管理器的commit()方法。

<?php
$session->commit();
?>

注意:根据https://php.ac.cn/manual/en/session.examples.basic.php,“会话通常在PHP完成执行脚本时自动关闭,但可以使用session_write_close()函数手动关闭。”commit()方法与session_write_close()等价。

要清除所有会话数据,但在当前请求中保持会话活动,请使用Session管理器的clear()方法。

<?php
$session->clear();
?>

要清除段上的所有闪存值,请使用clearFlash()方法。

要清除数据终止当前请求和未来请求的会话,从而完全销毁它,请调用destroy()方法。

<?php
$session->destroy(); // equivalent of session_destroy()
?>

调用 destroy() 也会通过 setcookie() 删除会话cookie。如果我们有其他删除cookie的方法,我们应该将一个可调用对象作为第二个参数传递给 SessionFactory 方法的 newInstance()。该可调用对象应接受三个参数:cookie名称、路径和域名。

<?php
// assume $response is a framework response object.
// this will be used to delete the session cookie.
$delete_cookie = function ($name, $path, $domain) use ($response) {
    $response->cookies->delete($name, $path, $domain);
}

$session = $session_factory->newInstance($_COOKIE, $delete_cookie);
?>

会话安全

会话ID重新生成

每当用户权限发生变化(即在系统中获得或失去访问权限)时,请务必重新生成会话ID

<?php
$session->regenerateId();
?>

注意:regenerateId() 方法也会重新生成CSRF令牌值。

跨站请求伪造

“跨站请求伪造”是一种安全漏洞,攻击者通过恶意JavaScript或其他方式,从客户端浏览器向用户已经认证的服务器发送盲请求。服务器认为请求是有效的,但实际上是伪造的,因为用户并没有真正发起请求(恶意JavaScript做了)。

http://en.wikipedia.org/wiki/Cross-site_request_forgery

防御CSRF攻击

为了防御CSRF攻击,服务器端逻辑应该

  1. 在每个表单中放置每个已认证用户会话的独特令牌值;并且

  2. 检查所有传入的POST/PUT/DELETE(即“不安全”)请求是否包含该值。

注意:如果我们的应用程序使用GET请求修改资源(这实际上是不正确的GET使用方式),我们还应该检查来自已认证用户的GET请求上的CSRF。

对于这个例子,表单字段名称将是 __csrf_value。在每个我们希望保护免受CSRF攻击的表单中,我们使用该字段的会话CSRF令牌值

<?php
/**
 * @var Vendor\Package\User $user A user-authentication object.
 * @var Aura\Session\Session $session A session management object.
 */
?>
<form method="post">

    <?php if ($user->auth->isValid()) {
        $csrf_value = $session->getCsrfToken()->getValue();
        echo '<input type="hidden" name="__csrf_value" value="'
           . htmlspecialchars($csrf_value, ENT_QUOTES, 'UTF-8')
           . '"></input>';
    } ?>

    <!-- other form fields -->

</form>

在处理请求时,检查传入的CSRF令牌是否对已认证用户有效

<?php
/**
 * @var Vendor\Package\User $user A user-authentication object.
 * @var Aura\Session\Session $session A session management object.
 */

$unsafe = $_SERVER['REQUEST_METHOD'] == 'POST'
       || $_SERVER['REQUEST_METHOD'] == 'PUT'
       || $_SERVER['REQUEST_METHOD'] == 'DELETE';

if ($unsafe && $user->auth->isValid()) {
    $csrf_value = $_POST['__csrf_value'];
    $csrf_token = $session->getCsrfToken();
    if (! $csrf_token->isValid($csrf_value)) {
        echo "This looks like a cross-site request forgery.";
    } else {
        echo "This looks like a valid request.";
    }
} else {
    echo "CSRF attacks only affect unsafe requests by authenticated users.";
}
?>

CSRF值生成

为了CSRF令牌有用,其随机值必须是加密安全的。使用像 mt_rand() 这样的方法是不足够的。Aura.Session附带一个实现 RandvalInterfaceRandval 类,并使用 opensslmcrypt 扩展来生成随机值。如果您没有安装这些扩展之一,您需要实现自己的 RandvalInterface 随机值实现。我们建议使用RandomLib的包装器。

会话有效期

我们可以使用Session对象的setCookieParams设置会话有效期,有效期可以是任意长或任意短。有效期以秒为单位。要将会话cookie有效期设置为两周

<?php
$session->setCookieParams(array('lifetime' => '1209600'));
?>

注意:setCookieParams 方法内部调用session_set_cookie_params