Yii 2 会话与 Cookie 完全指南:对象化封装、自定义存储与安全加固实践 后端Web框架【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址https://gitcode.com/gh_mirrors/yi/yii2点击查看免费下载本文基于 Yii 2 官方指南docs/guide-ja/runtime-sessions-cookies.md与英文版 docs/guide/runtime-sessions-cookies.md 内容一致编写。会话Session与 Cookie 是让数据跨多次用户请求持久化的基础机制在原生 PHP 中你只能通过全局变量$_SESSION和$_COOKIE访问它们而 Yii 2 将两者封装为对象提供面向对象的访问方式并附带实用的功能增强。读完本文你将掌握如何通过Yii::$app-session组件开关会话、读写会话数据与闪存消息如何把会话存储切换到数据库、缓存、Redis 或 MongoDB如何以对象方式读取与发送 Cookie以及如何配置 Cookie 验证、httpOnly/secure/sameSite等安全属性并正确设置与 Session 安全相关的php.ini参数。会话SessionYii 的对象化封装与请求runtime-requests和响应runtime-responses一样你可以通过session应用组件访问会话。默认情况下该组件是yii\web\Session的实例在代码中通过Yii::$app-session获取。$session Yii::$app-session;打开与关闭会话会话的打开与关闭通过以下方法完成$session Yii::$app-session; // 检查会话是否已打开 if ($session-isActive) ... // 打开会话 $session-open(); // 关闭会话 $session-close(); // 销毁会话中注册的所有数据 $session-destroy();你可以多次调用open()和close()而不会产生错误因为内部实现会首先检查会话是否已经打开。从源码看open()的开头即是if ($this-getIsActive()) { return; }而getIsActive()是对 PHPsession_status() PHP_SESSION_ACTIVE的封装见 framework/web/Session.php因此重复调用是幂等的。destroy()的内部实现则值得注意见 framework/web/Session.php它会先记录当前会话 ID、关闭会话再重新打开并执行session_unset()与session_destroy()最后恢复会话 ID。该操作仅在会话处于活动状态时生效因此调用前需确保已open()。访问会话数据访问会话中保存的数据有以下等价写法$session Yii::$app-session; // 获取会话变量。以下三种用法等价 $language $session-get(language); $language $session[language]; $language isset($_SESSION[language]) ? $_SESSION[language] : null; // 设置会话变量。以下三种用法等价 $session-set(language, en-US); $session[language] en-US; $_SESSION[language] en-US; // 删除会话变量。以下三种用法等价 $session-remove(language); unset($session[language]); unset($_SESSION[language]); // 检查会话变量是否存在。以下两种用法等价 if ($session-has(language)) ... if (isset($session[language])) ... // 遍历所有会话变量。以下两种用法等价 foreach ($session as $name $value) ... foreach ($_SESSION as $name $value) ...Info通过session组件访问会话数据时如果会话尚未打开组件会自动打开会话。这与直接通过$_SESSION访问不同——后者需要你显式调用session_start()。这一“自动打开”行为在源码中有清晰体现Session::get()、set()、remove()、has()等方法的第一行都是$this-open();见 framework/web/Session.php并且Session类实现了\IteratorAggregate、\ArrayAccess、\Countable接口见 framework/web/Session.php所以它能像数组一样被isset()、foreach、count()使用。数组型会话数据的限制与绕行方案当会话数据是数组时session组件存在一个限制无法直接修改数组中的某个元素。例如$session Yii::$app-session; // 下面的代码不会生效 $session[captcha][number] 5; $session[captcha][lifetime] 3600; // 下面的代码生效 $session[captcha] [ number 5, lifetime 3600, ]; // 下面的代码同样生效读取不限于顶层 echo $session[captcha][lifetime];原因在于ArrayAccess::offsetSet()只能接收整体值见 framework/web/Session.php它无法感知嵌套数组元素的写操作。要解决这个问题可以使用以下任意一种绕行方案$session Yii::$app-session; // 方案一直接使用 $_SESSION确保已调用 Yii::$app-session-open() $_SESSION[captcha][number] 5; $_SESSION[captcha][lifetime] 3600; // 方案二先取出整个数组修改后再整体存回 $captcha $session[captcha]; $captcha[number] 5; $captcha[lifetime] 3600; $session[captcha] $captcha; // 方案三用 ArrayObject 代替数组 $session[captcha] new \ArrayObject; ... $session[captcha][number] 5; $session[captcha][lifetime] 3600; // 方案四使用带公共前缀的键保存数组数据 $session[captcha.number] 5; $session[captcha.lifetime] 3600;为了兼顾性能与代码可读性官方推荐方案四与其把整个数组作为一个会话变量保存不如把数组的每个元素分别保存为独立的会话变量并让它们共享同一个键前缀。自定义会话存储默认的yii\web\Session类将会话数据以文件形式保存在服务器上。Yii 2 还提供了下列实现不同存储介质的会话类会话类存储介质yii\web\DbSession使用数据库表保存会话数据yii\web\CacheSession借助已配置的缓存组件使用缓存保存会话数据yii\redis\Session使用 Redis 作为存储介质由官方 redis 扩展提供yii\mongodb\Session将会话数据保存在 MongoDB 中由官方 mongodb 扩展提供所有这些会话类都支持同一套 API 方法。因此你可以在不修改任何使用会话的应用代码的前提下直接切换会话存储类。Note使用自定义会话存储时如果还想通过$_SESSION访问会话数据必须确保会话已经由Session::open()启动。这是因为自定义会话存储的处理器handler是在open()方法内部注册的。从源码看registerSessionHandler()会在open()中被调用见 framework/web/Session.php当getUseCustomStorage()返回true时它会创建并注册一个SessionHandler实例见 framework/web/Session.php。Note使用自定义会话存储时可能需要对会话垃圾回收器garbage collector进行显式配置。某些 PHP 安装例如 Debian将垃圾回收概率设为 0并依靠 cron 作业离线清理会话文件。这一流程对自定义存储并不适用因此你需要将yii\web\Session::$GCProbability配置为非零值。关于GCProbability源码的实现是把百分比换算成 PHP 的session.gc_probability/session.gc_divisor两个 ini 值见 framework/web/Session.php取值范围为 0100百分比。使用数据库表保存会话DbSession以下示例展示了如何在应用配置中把session组件配置为yii\web\DbSession从而使用数据库表作为会话存储return [ components [ session [ class yii\web\DbSession, // db mydb, // DB 连接的应用组件 ID。默认值为 db。 // sessionTable my_session, // 会话表名。默认值为 session。 ], ], ];你还需要创建一张如下结构的数据库表来保存会话数据CREATE TABLE session ( id CHAR(40) NOT NULL PRIMARY KEY, expire INTEGER, data BLOB )其中BLOB指你所用 DBMS 的 BLOB 类型。下面是几种常见 DBMS 可用的 BLOB 类型MySQLLONGBLOBPostgreSQLBYTEAMSSQLBLOBNote根据php.ini中session.hash_function的设置你可能需要调整id列的长度。例如如果设置为session.hash_functionsha256则应使用长度 64 而不是 40。DB 表结构定义与sessionTable属性的文档注释完全对应见 framework/web/DbSession.php。源码中还建议在生产环境为expire列建立索引以提升性能并提示 DbSession 在写会话时使用upsert语句、在回收时执行DELETE ... WHERE [[expire]]:expire见 framework/web/DbSession.php。作为替代方案你也可以使用下面的迁移来创建该表?php use yii\db\Migration; class m170529_050554_create_table_session extends Migration { public function up() { $this-createTable({{%session}}, [ id $this-char(64)-notNull(), expire $this-integer(), data $this-binary() ]); $this-addPrimaryKey(pk-id, {{%session}}, id); } public function down() { $this-dropTable({{%session}}); } }使用缓存保存会话CacheSession若想使用缓存保存会话数据可配置yii\web\CacheSessionreturn [ components [ session [ class yii\web\CacheSession, // cache mycache, // 缓存应用组件 ID。默认值为 cache。 ], ], ];需要注意从定义上讲缓存存储是易失的volatile其中的数据可能被换出而丢失。因此必须确保CacheSession使用的缓存不是易失的如果希望以数据库为存储介质DbSession是更稳妥的选择。源码层面CacheSession的读写分别通过$this-cache-get()/$this-cache-set()完成缓存键由[__CLASS__, $id]构成见 framework/web/CacheSession.php并且其getUseCustomStorage()恒返回true。闪存数据Flash Data闪存数据是一种特殊的会话数据它在某个请求中设置后仅在紧接着的下一次请求中可读之后会被自动删除。闪存数据最常见的用途是实现只向终端用户显示一次的消息例如用户成功提交表单后显示的确认信息。你可以通过session应用组件设置和访问闪存数据。例如$session Yii::$app-session; // 请求 #1 // 设置名为 postDeleted 的闪存消息 $session-setFlash(postDeleted, 投稿の削除に成功しました。); // 请求 #2 // 显示名为 postDeleted 的闪存消息 echo $session-getFlash(postDeleted); // 请求 #3 // 闪存消息已被自动删除因此 $result 为 false $result $session-hasFlash(postDeleted);与普通会话数据一样任意数据都可以作为闪存数据保存。调用setFlash()会覆盖同名已有的闪存数据如果希望向同名既有消息中追加新数据则应改用addFlash()。例如$session Yii::$app-session; // 请求 #1 // 在 alerts 名下追加多条闪存消息 $session-addFlash(alerts, 投稿の削除に成功しました。); $session-addFlash(alerts, 友達の追加に成功しました。); $session-addFlash(alerts, あなたのレベルが上りました。); // 请求 #2 // $alerts 是 alerts 名下的闪存消息数组 $alerts $session-getFlash(alerts);Note请不要对同名的闪存数据混用setFlash()与addFlash()。因为后一个方法会自动把闪存数据转换成数组以便追加结果调用getFlash()时你可能会根据这两个方法的调用顺序时而拿到数组、时而拿到字符串。从源码看闪存数据的生命周期由$flashParam默认键名__flash见 framework/web/Session.php下的计数器机制驱动setFlash()默认以-1标记“读取后删除”getFlash()读取时将其改为1而updateFlashCounters()会在下一个请求初始化时把计数大于 0 的闪存数据清除见 framework/web/Session.php。此外setFlash()/addFlash()还支持第三个参数$removeAfterAccess设为false时闪存消息在下一个请求结束时无论是否被读取都会被删除。Tip要显示闪存消息可以使用yii\bootstrap\Alert组件bootstrap Alert 部件方式如下echo Alert::widget([ options [class alert-info], body Yii::$app-session-getFlash(postDeleted), ]);Cookie请求与响应中的对象化表示Yii 把每一个 Cookie 表示为yii\web\Cookie的对象。yii\web\Request和yii\web\Response都通过名为cookies的属性维护一个 Cookie 集合yii\web\CookieCollection前者的集合表示请求中提交上来的 Cookie后者的集合表示将要发送给用户的 Cookie。直接操作请求与响应的应用部分是控制器Controller因此 Cookie 的读取与发送应在控制器中完成。读取 Cookie获取当前请求中的 Cookie// 从 request 组件获取 Cookie 集合yii\web\CookieCollection $cookies Yii::$app-request-cookies; // 获取名为 language 的 Cookie 的值若不存在则返回默认值 en $language $cookies-getValue(language, en); // 获取名为 language 的 Cookie 值的另一种写法 if (($cookie $cookies-get(language)) ! null) { $language $cookie-value; } // 也可以把 $cookies 当作数组使用 if (isset($cookies[language])) { $language $cookies[language]-value; } // 检查名为 language 的 Cookie 是否存在 if ($cookies-has(language)) ... if (isset($cookies[language])) ...CookieCollection::has()的实现值得注意值为空字符串、已标记删除expire 为过去时间的 Cookie 都会被视为不存在见 framework/web/CookieCollection.php。发送 Cookie使用下面的代码向终端用户发送 Cookie// 从 response 组件获取 Cookie 集合yii\web\CookieCollection $cookies Yii::$app-response-cookies; // 向将要发送的响应中添加一个新的 Cookie $cookies-add(new \yii\web\Cookie([ name language, value zh-CN, ])); // 删除一个 Cookie $cookies-remove(language); // 与下面的写法等价 unset($cookies[language]);除了示例中展示的name和value属性之外yii\web\Cookie类还定义了其他属性以完整表示所有可用的 Cookie 信息例如domain域、expire过期时间、path路径、secure仅 HTTPS、httpOnly仅 HTTP与sameSite同站策略。你可以在构造 Cookie 时按需配置这些属性再将其添加到响应组件的 Cookie 集合中$cookies-add(new \yii\web\Cookie([ name language, value zh-CN, domain example.com, path /, expire time() 86400 * 30, // 30 天后过期 secure true, // 仅在 TLS 连接上发送 httpOnly true, // 禁止客户端脚本访问 ]));注意CookieCollection分为只读与可写两种状态request组件的集合是只读的对其调用add()/remove()会抛出InvalidCallExceptionresponse组件的集合则可写见 framework/web/CookieCollection.php。remove()在默认情况下会把一个过期时间设为 1 的“删除标记”Cookie 加入响应集合从而让浏览器删除同名 Cookie。Cookie 验证防客户端篡改如最后两节所示通过request和response组件读写 Cookie 时你会额外获得Cookie 验证的安全保障它可以防止 Cookie 在客户端被篡改。其实现方式是为每个 Cookie 追加一个哈希字符串作为签名应用通过验证签名即可判断 Cookie 是否在客户端被修改。如果被修改该 Cookie 将无法通过request组件的 Cookie 集合访问。NoteCookie 验证只防止 Cookie 值被修改。即使验证失败你仍然可以通过$_COOKIE访问该 Cookie。这是为了让第三方库能够以不涉及 Cookie 验证的自身方式操作 Cookie。Cookie 验证默认启用你可以通过将yii\web\Request::enableCookieValidation属性设为false来禁用它——但官方强烈建议不要禁用。在源码中该属性默认值即为true见 framework/web/Request.php并且当启用验证时若未配置验证密钥会直接抛出InvalidConfigException见 framework/web/Request.php。Note直接通过$_COOKIE读取、或通过setcookie()发送的 Cookie 不会被验证。使用 Cookie 验证时必须指定一个用于生成上述哈希字符串的yii\web\Request::cookieValidationKey。你可以在应用配置中配置request组件来完成return [ components [ request [ cookieValidationKey ここに秘密のキーを書く, ], ], ];InfocookieValidationKey对应用安全至关重要。它只能被你信任的人知晓绝不能提交到版本控制系统中。从源码看请求组件解析 Cookie 时会在validateCookies()中调用安全组件Yii::$app-getSecurity()-validateData($value, $this-cookieValidationKey)进行签名校验见 framework/web/Request.php这再次印证了密钥的重要性——它是 Cookie 签名与验签的唯一凭据。Cookie 与 Session 的安全设置yii\web\Cookie与yii\web\Session都支持以下安全标志flag。httpOnly出于安全考虑yii\web\Cookie::httpOnly以及yii\web\Session::cookieParams中的httponly参数的默认值都被设为true。这有助于降低客户端脚本如 JavaScript访问受保护 Cookie 的风险在浏览器支持的前提下。相关默认值在源码中有明确体现Cookie::$httpOnly true见 framework/web/Cookie.php而Session的$_cookieParams [httponly true]见 framework/web/Session.php。securesecure标志的目的是防止 Cookie 以明文形式发送。在浏览器支持该标志的情况下只有当请求通过安全TLS连接发送时Cookie 才会被包含在请求中。配置方式在发送 Cookie 时设置secure true或通过Session::setCookieParams()将secure参数传给会话 Cookie。sameSite从 Yii 2.0.21 开始支持yii\web\Cookie::sameSite设置且要求 PHP 7.4.0 或更高版本对Session::cookieParams中的sameSite键源码要求 PHP 7.3.0 及以上见 framework/web/Session.php。sameSite设置的目的是防御 CSRF跨站请求伪造攻击在浏览器支持该设置的情况下只有符合指定策略Lax或Strict的 Cookie 才会被发送。为了安全在不支持该特性的 PHP 版本上使用sameSite会抛出异常。若想跨 PHP 版本使用该特性请先进行版本检查例如[ sameSite PHP_VERSION_ID 70300 ? yii\web\Cookie::SAME_SITE_LAX : null, ]源码中yii\web\Cookie定义了三个 SameSite 策略常量见 framework/web/Cookie.phpSAME_SITE_LAX Lax在跨站上下文中CSRF 高危请求方法如 POST、PUT、PATCH不会携带该 Cookie但普通链接跳转GET会携带SAME_SITE_STRICT Strict无论请求方法如何所有跨站上下文中都不携带该 Cookie即使是通过普通链接跳转SAME_SITE_NONE None自 2.0.43 起Cookie 在所有上下文中都会被发送注意若设置为None则必须同时将secure设为true否则浏览器会阻止该 Cookie。同时Session::setCookieParams()在 PHP 7.3.0 以下会通过把samesite拼接进path的方式降级兼容见 framework/web/Session.php。Note目前仍有部分浏览器不支持sameSite设置因此强烈建议同时采取附加的 CSRF 防护措施。与会话相关的 php.ini 设置如 PHP 手册所述php.ini中包含若干重要的会话安全设置。请务必应用官方推荐配置尤其是session.use_strict_mode——它在 PHP 安装中的默认状态下并未启用。该设置也可以通过yii\web\Session::useStrictMode属性进行配置return [ components [ session [ useStrictMode true, ], ], ];从源码看setUseStrictMode()最终通过ini_set(session.use_strict_mode, ...)生效见 framework/web/Session.php开启后Session::open()会在检测到未初始化的会话 ID 时强制调用regenerateID()重新生成会话 ID见 framework/web/Session.php从而防范会话固定session fixation攻击。这一行为在DbSession与CacheSession中还有更严格的实现开启严格模式后openSession()会先检查该 ID 在存储介质中是否真实存在若不存在则标记为需强制重新生成见 framework/web/DbSession.php。此外值得一并留意的相关 ini 项包括session.gc_probability/session.gc_divisor垃圾回收概率对应Session::$GCProbability默认 1440 秒的超时由session.gc_maxlifetime决定见 framework/web/Session.php、session.use_cookies/session.use_only_cookies会话 ID 的传递方式对应Session::$useCookies见 framework/web/Session.php。在多服务器部署等场景中请结合实际运行环境审慎调整这些参数并建议将会话存储切换为DbSession或CacheSession等共享存储以保证会话数据在多个节点之间一致可用。总结Yii 2 通过Yii::$app-session与request/response组件的cookies集合把原生 PHP 的$_SESSION、$_COOKIE全面对象化提供了幂等的开关方法、数组风格的读写语法、自动打开的会话生命周期以及一次性读取的闪存消息机制。在此基础上存储可插拔DbSession、CacheSession、redis\Session、mongodb\Session共享同一套 API可在不改业务代码的前提下切换会话存储Cookie 防篡改默认启用的 Cookie 验证 必须配置的cookieValidationKey让客户端篡改的 Cookie 无法通过request组件被读取安全默认值httpOnly默认开启sameSite自 2.0.21 起支持配合secure标志与php.ini的session.use_strict_mode可构建符合现代安全要求的会话与 Cookie 体系。需要深入研读实现时可以继续查看 framework/web/Session.php、framework/web/DbSession.php、framework/web/CacheSession.php、framework/web/Cookie.php、framework/web/CookieCollection.php 与 framework/web/Request.php并结合 docs/guide-ja/runtime-requests.md请求、docs/guide-ja/runtime-responses.md响应与 docs/guide-ja/security-best-practices.md安全最佳实践构建完整的运行时知识体系。赞分享后端Web框架【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址https://gitcode.com/gh_mirrors/yi/yii2点击查看免费下载相关推荐Yii 2 框架会话与 Cookie 实战指南从 $_SESSION 到面向对象封装、自定义存储与安全验证Yii 2 框架会话与 Cookie 实战指南从 $_SESSION 到面向对象封装、自定义存储与安全验证 导读 在 Yii 2 应用中跨请求保持用户状态后端Web框架Yii 2 运行时指南Session 与 Cookie 对象化封装、自定义存储与安全校验实战Yii 2 运行时指南Session 与 Cookie 对象化封装、自定义存储与安全校验实战 会话Session与 Cookie 是 Web 应用在多次请后端Web框架Yii 2 会话Session与 Cookie 完全指南面向对象的状态管理与 Cookie 签名验证Yii 2 会话Session与 Cookie 完全指南面向对象的状态管理与 Cookie 签名验证 会话Session与 Cookie 是 Web后端Web框架创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考