2 关注者

会话和 Cookie

会话和 Cookie 允许数据在多个用户请求之间持久化。在纯 PHP 中,您可以分别通过全局变量 $_SESSION$_COOKIE 访问它们。Yii 将会话和 Cookie 封装为对象,因此允许您以面向对象的方式访问它们,并提供其他有用的增强功能。

会话

请求响应 一样,您可以通过 session 应用程序组件 访问会话,该组件默认情况下是 yii\web\Session 的一个实例。

打开和关闭会话

要打开和关闭会话,您可以执行以下操作

$session = Yii::$app->session;

// check if a session is already open
if ($session->isActive) ...

// open a session
$session->open();

// close a session
$session->close();

// destroys all data registered to a session.
$session->destroy();

您可以多次调用 open()close() 而不导致错误;在内部,这些方法将首先检查会话是否已打开。

访问会话数据

要访问存储在会话中的数据,您可以执行以下操作

$session = Yii::$app->session;

// get a session variable. The following usages are equivalent:
$language = $session->get('language');
$language = $session['language'];
$language = isset($_SESSION['language']) ? $_SESSION['language'] : null;

// set a session variable. The following usages are equivalent:
$session->set('language', 'en-US');
$session['language'] = 'en-US';
$_SESSION['language'] = 'en-US';

// remove a session variable. The following usages are equivalent:
$session->remove('language');
unset($session['language']);
unset($_SESSION['language']);

// check if a session variable exists. The following usages are equivalent:
if ($session->has('language')) ...
if (isset($session['language'])) ...
if (isset($_SESSION['language'])) ...

// traverse all session variables. The following usages are equivalent:
foreach ($session as $name => $value) ...
foreach ($_SESSION as $name => $value) ...

信息: 当您通过 session 组件访问会话数据时,如果会话尚未打开,则会自动打开它。这与通过 $_SESSION 访问会话数据不同,后者需要显式调用 session_start()

当使用作为数组的会话数据时,session 组件存在一个限制,它阻止您直接修改数组元素。例如,

$session = Yii::$app->session;

// the following code will NOT work
$session['captcha']['number'] = 5;
$session['captcha']['lifetime'] = 3600;

// the following code works:
$session['captcha'] = [
    'number' => 5,
    'lifetime' => 3600,
];

// the following code also works:
echo $session['captcha']['lifetime'];

您可以使用以下解决方法之一来解决此问题

$session = Yii::$app->session;

// directly use $_SESSION (make sure Yii::$app->session->open() has been called)
$_SESSION['captcha']['number'] = 5;
$_SESSION['captcha']['lifetime'] = 3600;

// get the whole array first, modify it and then save it back
$captcha = $session['captcha'];
$captcha['number'] = 5;
$captcha['lifetime'] = 3600;
$session['captcha'] = $captcha;

// use ArrayObject instead of array
$session['captcha'] = new \ArrayObject;
...
$session['captcha']['number'] = 5;
$session['captcha']['lifetime'] = 3600;

// store array data by keys with a common prefix
$session['captcha.number'] = 5;
$session['captcha.lifetime'] = 3600;

为了获得更好的性能和代码可读性,我们推荐最后一种解决方法。也就是说,不要将数组存储为单个会话变量,而是将每个数组元素存储为一个会话变量,这些变量与其他数组元素共享相同的键前缀。

自定义会话存储

默认的 yii\web\Session 类将会话数据存储为服务器上的文件。Yii 还提供以下会话类,实现不同的会话存储

所有这些会话类都支持相同的 API 方法集。因此,您可以切换到不同的会话存储类,而无需修改使用会话的应用程序代码。

注意:如果您希望在使用自定义会话存储时通过 $_SESSION 访问会话数据,则必须确保会话已由 yii\web\Session::open() 启动。这是因为自定义会话存储处理程序是在此方法中注册的。

注意:如果您使用自定义会话存储,则可能需要显式配置会话垃圾回收器。某些 PHP 安装(例如 Debian)使用 0 的垃圾回收概率,并在 cronjob 中脱机清理会话文件。此过程不适用于您的自定义存储,因此您需要配置 yii\web\Session::$GCProbability 以使用非零值。

要了解如何配置和使用这些组件类,请参阅其 API 文档。以下是一个示例,展示了如何在应用程序配置中配置 yii\web\DbSession 以使用数据库表进行会话存储

return [
    'components' => [
        'session' => [
            'class' => 'yii\web\DbSession',
            // 'db' => 'mydb',  // the application component ID of the DB connection. Defaults to 'db'.
            // 'sessionTable' => 'my_session', // session table name. Defaults to 'session'.
        ],
    ],
];

您还需要创建以下数据库表来存储会话数据

CREATE TABLE session
(
    id CHAR(40) NOT NULL PRIMARY KEY,
    expire INTEGER,
    data BLOB
)

其中“BLOB”指的是您首选 DBMS 的 BLOB 类型。以下是可用于一些流行 DBMS 的 BLOB 类型

  • MySQL:LONGBLOB
  • PostgreSQL:BYTEA
  • MSSQL:BLOB

注意:根据 php.inisession.hash_function 的设置,您可能需要调整 id 列的长度。例如,如果 session.hash_function=sha256,则应使用长度 64 而不是 40。

或者,可以通过以下迁移来实现

<?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}}');
    }
}

闪存数据

闪存数据是一种特殊的会话数据,它在一次请求中设置后,仅在下一次请求中可用,并在之后自动删除。闪存数据最常用于实现仅应向最终用户显示一次的消息,例如用户成功提交表单后显示的确认消息。

您可以通过 session 应用程序组件设置和访问闪存数据。例如,

$session = Yii::$app->session;

// Request #1
// set a flash message named as "postDeleted"
$session->setFlash('postDeleted', 'You have successfully deleted your post.');

// Request #2
// display the flash message named "postDeleted"
echo $session->getFlash('postDeleted');

// Request #3
// $result will be false since the flash message was automatically deleted
$result = $session->hasFlash('postDeleted');

像常规会话数据一样,您可以将任意数据存储为闪存数据。

当您调用 yii\web\Session::setFlash() 时,它将覆盖具有相同名称的任何现有闪存数据。要将新的闪存数据附加到具有相同名称的现有消息,您可以改为调用 yii\web\Session::addFlash()。例如

$session = Yii::$app->session;

// Request #1
// add a few flash messages under the name of "alerts"
$session->addFlash('alerts', 'You have successfully deleted your post.');
$session->addFlash('alerts', 'You have successfully added a new friend.');
$session->addFlash('alerts', 'You are promoted.');

// Request #2
// $alerts is an array of the flash messages under the name of "alerts"
$alerts = $session->getFlash('alerts');

注意:请勿将 yii\web\Session::setFlash()yii\web\Session::addFlash() 结合使用以获取相同名称的闪存数据。这是因为后者方法会自动将闪存数据转换为数组,以便它可以附加相同名称的新闪存数据。因此,当您调用 yii\web\Session::getFlash() 时,您可能会发现有时您获得的是数组,而有时您获得的是字符串,具体取决于这两个方法调用的顺序。

提示:要显示闪存消息,您可以按照以下方式使用 yii\bootstrap\Alert 小部件

echo Alert::widget([
   'options' => ['class' => 'alert-info'],
   'body' => Yii::$app->session->getFlash('postDeleted'),
]);

Cookie

Yii 将每个 Cookie 表示为 yii\web\Cookie 的对象。 yii\web\Requestyii\web\Response 都通过名为 cookies 的属性维护 Cookie 集合。前者中的 Cookie 集合表示请求中提交的 Cookie,而后者中的 Cookie 集合表示要发送给用户的 Cookie。

直接处理请求和响应的应用程序部分是控制器。因此,Cookie 应该在控制器中读取和发送。

读取 Cookie

您可以使用以下代码获取当前请求中的 Cookie

// get the cookie collection (yii\web\CookieCollection) from the "request" component
$cookies = Yii::$app->request->cookies;

// get the "language" cookie value. If the cookie does not exist, return "en" as the default value.
$language = $cookies->getValue('language', 'en');

// an alternative way of getting the "language" cookie value
if (($cookie = $cookies->get('language')) !== null) {
    $language = $cookie->value;
}

// you may also use $cookies like an array
if (isset($cookies['language'])) {
    $language = $cookies['language']->value;
}

// check if there is a "language" cookie
if ($cookies->has('language')) ...
if (isset($cookies['language'])) ...

发送 Cookie

您可以使用以下代码将 Cookie 发送给最终用户

// get the cookie collection (yii\web\CookieCollection) from the "response" component
$cookies = Yii::$app->response->cookies;

// add a new cookie to the response to be sent
$cookies->add(new \yii\web\Cookie([
    'name' => 'language',
    'value' => 'zh-CN',
]));

// remove a cookie
$cookies->remove('language');
// equivalent to the following
unset($cookies['language']);

除了上述示例中显示的 namevalue 属性之外,yii\web\Cookie 类还定义了其他属性来完整表示所有可用的 Cookie 信息,例如 domainexpire。您可以根据需要配置这些属性以准备 Cookie,然后将其添加到响应的 Cookie 集合中。

当您通过 requestresponse 组件读取和发送 Cookie 时(如上一节中的两个小节所示),您可以享受 Cookie 验证带来的额外安全性,该验证可以防止 Cookie 在客户端被修改。这是通过使用哈希字符串对每个 Cookie 进行签名来实现的,这允许应用程序判断 Cookie 是否在客户端被修改。如果是,则无法通过 request 组件的 cookie 集合 访问该 Cookie。

注意:Cookie 验证仅保护 Cookie 值不被修改。如果 Cookie 未通过验证,您仍然可以通过 $_COOKIE 访问它。这是因为第三方库可能会以自己的方式操作 Cookie,这不会涉及 Cookie 验证。

Cookie 验证默认启用。您可以通过将 yii\web\Request::$enableCookieValidation 属性设置为 false 来禁用它,尽管我们强烈建议您不要这样做。

注意:通过 $_COOKIEsetcookie() 直接读取/发送的 Cookie 不会被验证。

在使用 Cookie 验证时,必须指定一个 yii\web\Request::$cookieValidationKey,该键将用于生成上述哈希字符串。您可以通过在应用程序配置中配置 request 组件来执行此操作

return [
    'components' => [
        'request' => [
            'cookieValidationKey' => 'fill in a secret key here',
        ],
    ],
];

信息:cookieValidationKey 对应用程序的安全性至关重要。它只能为您信任的人所知。不要将其存储在版本控制系统中。

安全设置

yii\web\Cookieyii\web\Session 都支持以下安全标志

httpOnly

为了提高安全性,yii\web\Cookie::$httpOnly 的默认值和 yii\web\Session::$cookieParams 的“httponly”参数都设置为 true。这有助于降低客户端脚本访问受保护的 Cookie 的风险(如果浏览器支持)。您可以阅读 HttpOnly wiki 文章 以了解更多详细信息。

secure

secure 标志的目的是防止 Cookie 以明文形式发送。如果浏览器支持 secure 标志,它只会在线程安全(TLS)连接发送请求时包含 Cookie。您可以阅读 SecureFlag wiki 文章 以了解更多详细信息。

sameSite

从 Yii 2.0.21 开始,支持 yii\web\Cookie::$sameSite 设置。它需要 PHP 7.3.0 或更高版本。sameSite 设置的目的是防止 CSRF(跨站点请求伪造)攻击。如果浏览器支持 sameSite 设置,它将根据指定的策略(“Lax”或“Strict”)包含 Cookie。您可以阅读 SameSite wiki 文章 以了解更多详细信息。为了提高安全性,如果在不受支持的 PHP 版本中使用 sameSite,则会抛出异常。要在不同 PHP 版本中使用此功能,请先检查版本。例如:

[
    'sameSite' => PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null,
]

注意:由于并非所有浏览器都支持 sameSite 设置,因此仍然强烈建议您还包括 其他 CSRF 保护

会话 php.ini 设置

PHP 手册中所述php.ini 具有重要的会话安全设置。请确保应用推荐的设置。尤其是 session.use_strict_mode,它在 PHP 安装中默认未启用。此设置也可以使用 yii\web\Session::$useStrictMode 进行设置。

发现错别字或您认为此页面需要改进?
在 github 上编辑它 !