ocramius/psr7-session

此包已被废弃且不再维护。作者建议使用 psr7-sessions/storageless 包。

无存储 PSR-7 会话支持

9.1.0 2023-11-20 14:53 UTC

README

Mutation testing badge Type Coverage Packagist Packagist

PSR7Session 是一个与 PSR-7PSR-15 兼容的 中间件,允许在基于 PSR-7 的应用程序中实现无 I/O 的会话。

ocramiusmalukenholcobucci 带来。

安装

composer require psr7-sessions/storageless

用法

您可以在任何 PSR-15 兼容的中间件中使用 PSR7Sessions\Storageless\Http\SessionMiddleware

在一个 mezzio/mezzio 应用程序中,这看起来像以下这样

use Lcobucci\JWT\Configuration as JwtConfig;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key\InMemory;
use PSR7Sessions\Storageless\Http\SessionMiddleware;
use PSR7Sessions\Storageless\Http\Configuration as StoragelessConfig;

$app = new \Mezzio\Application(/* ... */);

$app->pipe(new SessionMiddleware(
    new StoragelessConfig(
        JwtConfig::forSymmetricSigner(
            new Signer\Hmac\Sha256(),
            InMemory::base64Encoded('OpcMuKmoxkhzW0Y1iESpjWwL/D3UBdDauJOe742BJ5Q='), // replace this with a key of your own (see below)
        )
    )
));

之后,您可以在任何可以访问 Psr\Http\Message\ServerRequestInterface 属性的中间件中访问会话数据

$app->get('/get', function (ServerRequestInterface $request, ResponseInterface $response) : ResponseInterface {
    /** @var \PSR7Sessions\Storageless\Session\SessionInterface $session */
    $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
    $session->set('counter', $session->get('counter', 0) + 1);

    $response
        ->getBody()
        ->write('Counter Value: ' . $session->get('counter'));

    return $response;
});

由于不涉及超全局变量或 I/O,您也可以在异步环境和长时间运行的过程中这样做。

建议您使用具有大量熵的密钥,最好使用加密安全的伪随机数生成器 (CSPRNG) 生成。您可以使用 CryptoKey 工具 来完成此操作。

请注意,您也可以使用非对称密钥;请参阅 lcobucci/jwt 文档

  1. 配置对象: https://lcobucci-jwt.readthedocs.io/en/stable/configuration/
  2. 支持算法: https://lcobucci-jwt.readthedocs.io/en/stable/supported-algorithms/

会话劫持缓解

为了缓解与 cookie 盗窃相关的风险以及因此产生的 会话劫持,您可以通过启用客户端指纹识别将用户会话绑定到其 IP(`$_SERVER['REMOTE_ADDR']`)和 User-Agent(`$_SERVER['HTTP_USER_AGENT']`)

use PSR7Sessions\Storageless\Http\SessionMiddleware;
use PSR7Sessions\Storageless\Http\Configuration as StoragelessConfig;
use PSR7Sessions\Storageless\Http\ClientFingerprint\Configuration as FingerprintConfig;

$app = new \Mezzio\Application(/* ... */);

$app->pipe(new SessionMiddleware(
    (new StoragelessConfig(/* ... */))
        ->withClientFingerprintConfiguration(
            FingerprintConfig::forIpAndUserAgent()
        )
));

如果您的 PHP 服务位于您的反向代理后面,您可能需要从不同的来源检索客户端 IP。在这种情况下,您可以通过编写自定义的 \PSR7Sessions\Storageless\Http\ClientFingerprint\Source 实现来提取所需的信息

use Psr\Http\Message\ServerRequestInterface;
use PSR7Sessions\Storageless\Http\SessionMiddleware;
use PSR7Sessions\Storageless\Http\Configuration as StoragelessConfig;
use PSR7Sessions\Storageless\Http\ClientFingerprint\Configuration as FingerprintConfig;
use PSR7Sessions\Storageless\Http\ClientFingerprint\Source;

$app = new \Mezzio\Application(/* ... */);

$app->pipe(new SessionMiddleware(
    (new StoragelessConfig(/* ... */))
        ->withClientFingerprintConfiguration(
            FingerprintConfig::forSources(new class implements Source{
                 public function extractFrom(ServerRequestInterface $request): string
                 {
                     return $request->getHeaderLine('X-Real-IP');
                 }
            })
        )
));

示例

只需在控制台浏览到 `examples` 目录,然后运行

php -S localhost:9999 index.php

然后尝试访问 `http://localhost:9999`:您应该看到在页面刷新时增加的计数器

WHY?

在大多数 PHP+HTTP 相关的项目中,`ext/session` 能够满足其目的,并允许我们通过将某个标识符与访问用户代理相关联来存储服务器端信息。

`ext/session` 的问题是什么?

这听起来都很不错,但是

  • 依赖于 `$_SESSION` 超全局变量
  • 依赖于关闭处理程序以“提交”会话到存储
  • 由于存储而具有大量活动的用户限制
  • 由于存储而有大量的 I/O
  • 存在跨不同进程的序列化数据(PHP 会为您序列化和反序列化 $_SESSION,并且存在安全影响)
  • 必须使用集中式存储来扩展横向扩展的配置
  • 当存储不是集中式时,必须使用粘性会话(带“智能”负载均衡器)
  • 未设计用于多轮调度周期

本项目做什么?

本项目尝试实现无存储的会话并缓解上述问题。

假设

  • 您的会话相对较小,仅包含少量标识符和一些 CSRF 令牌。小型意味着 < 400 字节
  • 会话中的数据是 JsonSerializable 或等效的
  • 会话中的数据可以被客户端 自由读取

它是如何工作的?

会话数据直接存储在会话 cookie 中作为 JWT 令牌。

这种方法并不新颖,通常与 HTTP/REST/OAuth API 中的 Bearer 令牌一起使用。

为了确保会话数据未被修改,客户端可以信任信息,并且服务器和客户端之间有共同的到期日期,使用 JWT 令牌 传输信息。

JWT 令牌始终签名,以确保用户代理永远不会能够操纵会话。支持使用对称和非对称密钥进行签名/验证令牌。

优势

  • 无需存储
  • 无需粘性会话(任何拥有私有或公共密钥副本的服务器都可以生成会话或消费它们)
  • 可以向客户端传输明文信息,允许它与服务器共享一些信息(一个标准的例子是在会话中共享“用户名”或“用户-id”)
  • 可以向客户端传输加密信息,允许服务器仅消费信息
  • 不受 PHP 序列化 RCE 攻击的影响
  • 不受 PHP 进程作用域限制:每个进程可以有多个会话
  • 不依赖于全局状态
  • 在多服务器设置中,您可能允许仅对具有访问公共密钥的服务器进行只读访问,而写入则限于具有访问私有密钥的服务器
  • 可以在多轮调度周期中使用

配置选项

请参阅配置文档

已知限制

请参阅限制文档

贡献

请参阅贡献说明

许可证

本项目在 MIT 许可证 下公开发布。