snicco / session
适用于 $_SESSION 无法使用的环境的独立会话实现。
Requires
- php: ^7.4|^8.0
- ext-filter: *
- paragonie/constant_time_encoding: ^2.4
- snicco/str-arr: ^2.0
- snicco/testable-clock: ^2.0
Requires (Dev)
- cache/array-adapter: ^1.0.0
- phpunit/phpunit: ^9.5.13
- snicco/session-testing: ^2.0
Conflicts
- snicco/better-wp-api: <2.0.0-beta.9
- snicco/better-wp-cache: <2.0.0-beta.9
- snicco/better-wp-cache-bundle: <2.0.0-beta.9
- snicco/better-wp-cli: <2.0.0-beta.9
- snicco/better-wp-cli-testing: <2.0.0-beta.9
- snicco/better-wp-hooks: <2.0.0-beta.9
- snicco/better-wp-hooks-bundle: <2.0.0-beta.9
- snicco/better-wp-mail: <2.0.0-beta.9
- snicco/better-wp-mail-bundle: <2.0.0-beta.9
- snicco/better-wp-mail-testing: <2.0.0-beta.9
- snicco/better-wpdb: <2.0.0-beta.9
- snicco/better-wpdb-bundle: <2.0.0-beta.9
- snicco/blade-bridge: <2.0.0-beta.9
- snicco/blade-bundle: <2.0.0-beta.9
- snicco/content-negotiation-middleware: <2.0.0-beta.9
- snicco/debug-bundle: <2.0.0-beta.9
- snicco/default-headers-middleware: <2.0.0-beta.9
- snicco/eloquent: <2.0.0-beta.9
- snicco/encryption-bundle: <2.0.0-beta.9
- snicco/event-dispatcher: <2.0.0-beta.9
- snicco/event-dispatcher-testing: <2.0.0-beta.9
- snicco/guests-only-middleware: <1.0.0
- snicco/http-routing: <2.0.0-beta.9
- snicco/http-routing-bundle: <2.0.0-beta.9
- snicco/http-routing-testing: <2.0.0-beta.9
- snicco/https-only-middleware: <2.0.0-beta.9
- snicco/illuminate-container-bridge: <2.0.0-beta.9
- snicco/kernel: <2.0.0-beta.9
- snicco/kernel-testing: <2.0.0-beta.9
- snicco/method-override-middleware: <2.0.0-beta.9
- snicco/minimal-logger: <2.0.0-beta.9
- snicco/must-match-route-middleware: <2.0.0-beta.9
- snicco/no-robots-middleware: <2.0.0-beta.9
- snicco/open-redirect-protection-middleware: <2.0.0-beta.9
- snicco/payload-middleware: <2.0.0-beta.9
- snicco/pimple-bridge: <2.0.0-beta.9
- snicco/psr7-error-handler: <2.0.0-beta.9
- snicco/redirect-middleware: <2.0.0-beta.9
- snicco/session-bundle: <2.0.0-beta.9
- snicco/session-psr16-bridge: <2.0.0-beta.9
- snicco/session-testing: <2.0.0-beta.9
- snicco/session-wp-bridge: <2.0.0-beta.9
- snicco/share-cookies-middleware: <2.0.0-beta.9
- snicco/signed-url: <2.0.0-beta.9
- snicco/signed-url-psr15-bridge: <2.0.0-beta.9
- snicco/signed-url-psr16-bridge: <2.0.0-beta.9
- snicco/signed-url-testing: <2.0.0-beta.9
- snicco/signed-url-wp-bridge: <2.0.0-beta.9
- snicco/templating: <2.0.0-beta.9
- snicco/templating-bundle: <2.0.0-beta.9
- snicco/testing-bundle: <2.0.0-beta.9
- snicco/trailing-slash-middleware: <2.0.0-beta.9
- snicco/wp-auth-only-middleware: <2.0.0-beta.9
- snicco/wp-capability-middleware: <2.0.0-beta.9
- snicco/wp-capapility-middleware: <1.0.0
- snicco/wp-guests-only-middleware: <2.0.0-beta.9
- snicco/wp-nonce-middleware: <2.0.0-beta.9
- dev-master
- v2.0.0-beta.9
- v2.0.0-beta.8
- v2.0.0-beta.7
- v2.0.0-beta.6
- v2.0.0-beta.5
- v2.0.0-beta.4
- v2.0.0-beta.3
- v2.0.0-beta.2
- v2.0.0-beta.1
- v1.10.1
- v1.10.0
- v1.9.1
- v1.9.0
- v1.8.1
- v1.8.0
- v1.7.0
- v1.6.2
- v1.6.1
- v1.6.0
- v1.5.0
- v1.4.2
- v1.4.1
- v1.4.0
- v1.3.0
- v1.2.1
- v1.2.0
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.2
- v1.0.1
- v1.0.0
- dev-beta
This package is auto-updated.
Last update: 2024-09-07 14:27:56 UTC
README
目录
动机
虽然 PHP 的原生 $_SESSION 对于大多数用例都很好,但在某些环境中并不理想。其中两个是分布式 WordPress 代码 或 PSR7/PSR15 应用程序。
Snicco 项目 的 会话 组件是一个完全独立的库,不依赖于任何框架。
功能
-
自动处理 失效、轮换 和 空闲超时。
-
非阻塞。
-
跟踪会话是否已更改,并且只有在需要时才更新(而不影响超时)。
-
只接受服务器端生成的会话 ID。
-
支持许多存储后端,全部在各自的 composer 包中。
-
使用 paragonie 的分令牌方法 来防止 基于时间的旁路攻击。
-
设计上安全,通过破坏存储后端(假设只读访问)无法劫持会话 ID。
-
PSR-7/15 兼容。没有对 PHP 超全局变量的隐藏依赖。
-
区分 可变 和 不可变 会话对象。
-
选择将您的会话数据
json_encoding
或serializing
。或者提供您自己的规范化程序。 -
支持加密和解密会话数据(通过接口,别担心)。
-
基于用户 ID 的高级会话管理。
-
支持闪存消息和旧输入。
-
100% 测试覆盖率和 100% psalm 类型覆盖率。
安装
composer require snicco/session
使用
创建会话配置
use Snicco\Component\Session\ValueObject\SessionConfig; $configuration = new SessionConfig([ // The path were the session cookie will be available 'path' => '/', // The session cookie name 'cookie_name' => 'my_app_sessions', // This should practically never be set to false 'http_only' => true, // This should practically never be set to false 'secure' => true, // one of "Lax"|"Strict"|"None" 'same_site' => 'Lax', // A session with inactivity greater than the idle_timeout will be regenerated and flushed 'idle_timeout_in_sec' => 60 * 15, // Rotate session ids periodically 'rotation_interval_in_sec' => 60 * 10, // Setting this value to NULL will make the session a "browser session". // Setting this to any positive integer will mean that the session will be regenerated and flushed // independently of activity. 'absolute_lifetime_in_sec' => null, // The percentage that any given call to SessionManager::gc() will trigger garbage collection // of inactive sessions. 'garbage_collection_percentage' => 2, ]);
创建序列化器
此包附带两个内置序列化器
JsonSerializer
,它假定您的所有会话内容都是JsonSerializable
或等效的。PHPSerializer
,它将使用serialize
和unserialize
。
如果这些都不起作用,您只需实现 Serializer
接口。
创建会话驱动器
SessionDriver
是一个 接口
,它抽象化了会话数据的具体存储后端。
目前,以下驱动器可用
-
InMemoryDriver
,用于测试期间的使用。 -
EncryptedDriver
,接受一个SessionDriver
作为参数,并对其数据进行加密/解密。 -
Psr16Driver
,允许您使用任何PSR-16缓存。您可以通过使用snicco/session-psr16-bridge
来使用此驱动程序。 -
WPDBDriver
,您可以使用snicco/session-wp-bridge
通过WordPress数据库存储会话。 -
WP_Object_Cache
,您可以使用snicco/session-wp-bridge
通过WordPress对象缓存存储会话。 -
Custom
,如果您上述任何驱动程序都不适用(并且没有PSR-16
适配器),您可以使用snicco/session-testing
来测试您的自定义实现与接口。
创建会话管理器
SessionManager
负责创建和持久化Session
对象。
use Snicco\Component\Session\SessionManager\SessionManger; $configuration = /* */ $serializer = /* */ $driver = /* */ $session_manger = new SessionManger($configuration, $driver, $serializer);
启动会话
SessionManager
使用CookiePool
的实例来启动会话。
您可以从$_COOKIE
超级全局变量或任何普通的array
实例化此对象。
调用SessionManger::start()
将处理
- 如果提供的id在驱动程序中找不到(或不存在),则拒绝会话id并生成一个新的空会话。
- 根据您的配置旋转会话id。
- 根据您的配置旋转和清除空闲的会话。
use Snicco\Component\Session\SessionManager\SessionManger; use Snicco\Component\Session\ValueObject\CookiePool; $configuration = /* */ $serializer = /* */ $driver = /* */ $session_manger = new SessionManger($configuration, $driver, $serializer); // using $_COOKIE $cookie_pool = CookiePool::fromSuperGlobals(); // or any array. $cookie_pool = new CookiePool($psr7_request->getCookieParams()); $session = $session_manger->start($cookie_pool);
调用SessionManager::start()
将返回Session
的实例。 Session
是一个接口,它扩展了MutableSession
接口和ImmutableSession
接口。
这允许您清楚地分离对会话的读取和写入的不同关注点。
在您的代码中,您应该依赖于MutableSession
或ImmutableSession
。
Session
接口仅用于通过会话管理器持久化会话。
不可变会话
ImmutableSession
仅包含返回数据的return
方法。无法修改会话。
use Snicco\Component\Session\ImmutableSession; use Snicco\Component\Session\Session; use Snicco\Component\Session\ValueObject\ReadOnlySession; /** * @var Session $session */ $session = $session_manger->start($cookie_pool); // You can either rely on type-hints or transform $session to an immutable object like so: $read_only_session = ReadOnlySession::fromSession($session); function readFromSession(ImmutableSession $session) { $session->id(); // instance of SessionId $session->isNew(); // true/false $session->userId(); // int|string|null $session->createdAt(); // timestamp. Can never be changed. $session->lastRotation(); // timestamp $session->lastActivity(); // last activity is updated each time a session is saved. $session->has('foo'); // true/false $session->boolean('wants_beta_features'); // true/false $session->only(['foo', 'bar']); // only get keys "foo" and "bar" $session->get('foo', 'default'); // get key "foo" with optional default value $session->all(); // Returns array of all user provided data. $session->oldInput('username', ''); // Old input is flushed after saving a session twice. $session->hasOldInput('username'); // true/false $session->missing(['foo', 'bar']); // Returns true if all the given keys are not in the session. $session->missing(['foo', 'bar']); // Returns true if all the given keys are in the session. }
可变会话
Mutable
仅包含修改数据的modify
方法。无法读取会话数据。
use Snicco\Component\Session\MutableSession; use Snicco\Component\Session\Session; use Snicco\Component\Session\ValueObject\ReadOnlySession; /** * @var Session $session */ $session = $session_manger->start($cookie_pool); function modifySession(MutableSession $session) { // Store the current user after authentication. $session->setUserId('user-1'); // can be int|string $session->setUserId(1); // Rotates the session id and flushes all data. $session->invalidate(); // Rotates the session id WITHOUT flushing data. $session->rotate(); $session->put('foo', 'bar'); $session->put(['foo' => 'bar', 'baz' => 'biz']); $session->putIfMissing('foo', 'bar'); $session->increment('views'); $session->increment('views', 2); // Increment by 2 $session->decrement('views'); $session->decrement('views', 2); // Decrement by 2 $session->push('viewed_pages', 'foo-page'); // Push a value onto an array. $session->remove('foo'); $session->flash('account_created', 'Your account was created'); // account_created is only available during the current request and the next request. $session->flashNow('account_created', 'Your account was created' ); // account_created is only available during the current request. $session->flashInput('login_form.email', 'calvin@snicco.io'); // This value is available during the current request and the next request. $session->reflash(); // Reflash all flash data for one more request. $session->keep(['account_created']); // Keep account created for one more request. $session->flush(); // Empty the session data. }
访问嵌套数据
可以使用"点"访问嵌套数据。
$session->put([ 'foo' => [ 'bar' => 'baz' ] ]); var_dump($session->get('foo.bar')); // baz
闪存消息 / 旧输入
将数据闪存到会话中意味着仅在会话保存两次后才存储它。
此用法最常见的场景是在POST
请求后显示通知。
// POST request: // create user account and redirect to success page. $session->flash('account_created', 'Great! Your account was created.'); // session is saved. // GET request: echo $session->get('account_created'); // session is saved again, account_created is now gone.
旧输入的工作方式非常相似。最常见的用例是在表单验证失败时显示提交的表单数据。
// POST request: $username = $_POST['username']; // validate the request... // Validation failed. $session->flashInput('username', $username); // session is saved. // GET request: if($session->hasOldInput('username')) { $username = $session->oldInput('username'); // Use username to populate the form values again. } // session is saved again, username is now gone.
加密会话数据
如果您在会话中存储敏感数据,可以使用EncryptedDriver
。
此驱动程序将包装另一个(内部)会话驱动程序,并在将其传递给应用程序代码之前加密/解密您的数据。
要正常工作,EncryptedDriver
需要一个SessionEncryptor
实例,这是一个没有实现且极其简单的接口。
以下是使用defuse/php-encryption
加密会话的示例。
use Snicco\Component\Session\Driver\EncryptedDriver; use Snicco\Component\Session\SessionEncryptor; final class DefuseSessionEncryptor implements SessionEncryptor { private string $key; public function __construct(string $key) { $this->$key = $key; } public function encrypt(string $data): string { return Defuse\Crypto\Crypto::encrypt($data, $this->key); } public function decrypt(string $data): string { return Defuse\Crypto\Crypto::decrypt($data, $this->key); } } $driver = new EncryptedDriver( $inner_driver, new DefuseSessionEncryptor($your_key) )
保存会话
Session
是一个值对象。只有在会话管理器将其保存时,会话中的更改才会被持久化。
一旦会话被保存,它就会被锁定。在对已锁定的会话调用任何状态更改方法时,将抛出SessionIsLocked
异常。
对未修改的会话调用save
方法,将仅使用SessionDriver::touch()
更新会话的最后活动时间。
这可以消除由重叠的GET/POST请求引起的许多竞争条件,这些请求读取和写入会话。
use Snicco\Component\Session\SessionManager\SessionManger; use Snicco\Component\Session\ValueObject\CookiePool; $configuration = /* */ $serializer = /* */ $driver = /* */ $cookie_pool = /* */; $session_manger = new SessionManger($configuration, $driver, $serializer); $session = $session_manger->start($cookie_pool); $session->put('foo', 'bar'); $session_manger->save($session); // This will throw an exception. $session->put('foo', 'baz');
设置会话cookie
设置cookie超出了这个库的作用范围(因为我们不知道你在应用程序中如何处理HTTP问题)。
相反,会话管理器提供了一个方法,可以从会话中检索一个SessionCookie
值对象。
以下是如何使用SessionCookie
类来使用setcookie
设置会话cookie的示例。如果你使用的是**PSR-7**请求,你可以做类似的事情。
use Snicco\Component\Session\SessionManager\SessionManger; use Snicco\Component\Session\ValueObject\CookiePool; $configuration = /* */ $serializer = /* */ $driver = /* */ $cookie_pool = /* */; $session_manger = new SessionManger($configuration, $driver, $serializer); $session = $session_manger->start($cookie_pool); $session->put('foo', 'bar'); $session_manger->save($session); $cookie = $session_manger->toCookie($session); $same_site = $cookie->sameSite(); $same_site = ('None; Secure' === $same_site) ? 'None' : $same_site; setcookie($cookie->name(), $cookie->value(), [ 'expires' => $cookie->expiryTimestamp(), 'samesite' => $same_site, 'secure' => $cookie->secureOnly(), 'path' => $cookie->path(), 'httponly' => $cookie->httpOnly(), ]);
基于用户ID管理会话
将用户ID存储在会话中不是强制性的。
然而,如果你选择这样做,这个包提供了一些管理基于用户ID的会话的出色工具。
UserSessionsDriver
扩展了SessionDriver
接口。
并不是所有驱动程序都支持这个接口。
use Snicco\Component\Session\Driver\InMemoryDriver; // The in memory driver implements UserSessionDriver $in_memory_driver = new InMemoryDriver(); // Destroy all sessions, for all users. $in_memory_driver->destroyAllForAllUsers(); // Destroys all sessions where the user id has been set to (int) 12. // Useful for "log me out everywhere" functionality. $in_memory_driver->destroyAllForUserId(12); $session_selector = $session->id()->selector(); // Destroys all sessions for user 12 expect the passed one. // Useful for "log me out everywhere else" functionality. $in_memory_driver->destroyAllForUserIdExcept($session_selector, 12); // Returns an array of SerializedSessions for user 12. $in_memory_driver->getAllForUserId(12);
垃圾回收
你应该在每次使用会话的请求中调用SessionManager::gc()
。
// That's it, this will remove all idle sessions with the percentage that you configured. $session_manager->gc();
贡献
这个存储库是Snicco项目的开发存储库的只读分支。
报告问题和发送拉取请求
请在Snicco单一代码库中报告问题。
安全性
如果您发现一个安全漏洞,请遵循我们的披露程序。