客戶端與服務(wù)器通信璧亮,這是一個(gè)request
與response
的過(guò)程。在Yii2
中分別有yii\web\Request.php
和 yii\web\Response.php
來(lái)處理斥难。
其中request處理cookie
的邏輯是:
<?php
protected function loadCookies()
{
$cookies = [];
if ($this->enableCookieValidation) {
if ($this->cookieValidationKey == '') {
throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
}
foreach ($_COOKIE as $name => $value) {
if (is_string($value) && ($value = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey)) !== false) {
$cookies[$name] = new Cookie([
'name' => $name,
'value' => @unserialize($value),
'expire'=> null
]);
}
}
} else {
foreach ($_COOKIE as $name => $value) {
$cookies[$name] = new Cookie([
'name' => $name,
'value' => $value,
'expire'=> null
]);
}
}
return $cookies;
}
這里request從PHP
的全局變量$_COOKIE
中獲取客戶端傳的cookie
, 構(gòu)造yii\web\Cookie
對(duì)象枝嘶,而這個(gè)對(duì)象只有name和value屬性賦值了
response中處理cookie
的邏輯是:
首先通過(guò)Yii::$app->response->cookies
調(diào)用yii\web\CookieCollection.php
:
<?php
public function getCookies()
{
if ($this->_cookies === null) {
$this->_cookies = new CookieCollection;
}
return $this->_cookies;
}
不論是新增,還是刪除,修改cookie
哑诊,都是通過(guò)將cookie
賦值給Yii::$app->response->cookies
最后response通過(guò)sendCookies()
返回給客戶端:
<?php
protected function sendCookies()
{
if ($this->_cookies === null) {
return;
}
$request = Yii::$app->getRequest();
if ($request->enableCookieValidation) {
if ($request->cookieValidationKey == '') {
throw new InvalidConfigException(get_class($request) . '::cookieValidationKey must be configured with a secret key.');
}
$validationKey = $request->cookieValidationKey;
}
foreach ($this->getCookies() as $cookie) {
$value = $cookie->value;
if ($cookie->expire != 1 && isset($validationKey)) {
$value = Yii::$app->getSecurity()->hashData(serialize($value), $validationKey);
}
setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
}
$this->getCookies()->removeAll();
}
默認(rèn)的我們的cookie
是寫到當(dāng)前域名下面的, 對(duì)于一般場(chǎng)景這是沒(méi)問(wèn)題的群扶。當(dāng)時(shí)對(duì)于前后端分離的應(yīng)用,尤其是前后端域名不一致的場(chǎng)景镀裤,就會(huì)出現(xiàn)問(wèn)題竞阐。
假定現(xiàn)在有一個(gè)應(yīng)用采用前后端分離的架構(gòu),前端域名為: mike.com
, 后端域名為: api.mike.com
. 后端使用cookie
存儲(chǔ)用戶名:
{
"name": "user_name",
"value": "mike",
"domain": ".mike.com",
"path": "/"
}
前臺(tái)登錄淹禾,調(diào)用API http://api.mike.com/site/login
, 后臺(tái)代碼:
<?php
public function actionLogin()
{
// validate username and passwd
// set cookie
$cookie = new yii\web\Cookie();
$cookie->name = 'user_name';
$cookie->value = 'mike';
$cookie->domain = 'mike.com';
// 注意這里的domain不能不填馁菜,否則會(huì)使用api.mike.com, 這樣mike.com就不會(huì)拿到cookie
Yii::$app->response->cookies->add($cookie);
}
當(dāng)前臺(tái)退出登錄,調(diào)用API http://api.mike.com/site/logout
清除cookie, 后臺(tái)代碼如下:
<?php
public function actionLogout()
{
Yii::$app->response->cookies->remove('user_name');
}
你以為你清除了cookie user_name, 然而并不是這樣铃岔, 你可以通過(guò)chrome瀏覽器F12
在Application
中查看當(dāng)前域名下面存在的cookie
為什么會(huì)不會(huì)刪除呢汪疮? 讓我們先看看reomve
的邏輯:
<?php
public function remove($cookie, $removeFromBrowser = true)
{
if ($this->readOnly) {
throw new InvalidCallException('The cookie collection is read only.');
}
if ($cookie instanceof Cookie) {
$cookie->expire = 1;
$cookie->value = '';
} else {
$cookie = new Cookie([
'name' => $cookie,
'expire' => 1,
]);
}
if ($removeFromBrowser) {
$this->_cookies[$cookie->name] = $cookie;
} else {
unset($this->_cookies[$cookie->name]);
}
}
如果傳的不是一個(gè)Cookie
對(duì)象,那么會(huì)構(gòu)造一個(gè)Cookie
對(duì)象毁习,設(shè)置expire
, 添加到response中智嚷,服務(wù)器收到一個(gè)expire = 1
的cookie
, 就會(huì)從本地刪除,之后的請(qǐng)求就不會(huì)帶這個(gè)name的cookie
了纺且。問(wèn)題在于重新構(gòu)造的cookie沒(méi)有指定domain, 那么就會(huì)用當(dāng)前域名api.mike.com盏道; 可是login寫的cookie domain為mike.com, 雖然name相同,都是user_name, 但是domain不同载碌,瀏覽器不認(rèn)為是一個(gè)cookie, 所以login寫入的cookie不會(huì)被刪除,
改進(jìn)后的代碼:
<?php
public function actionLogout()
{
$cookie = new yii\web\Cookie();
$cookie->domain = 'mike.com';
$cookie->name = 'user_name';
Yii::$app->response->cookies->remove($cookie);
}
其實(shí)不僅remove
, 去更新cookie的時(shí)候也是會(huì)存在這個(gè)問(wèn)題的猜嘱。
<?php
//錯(cuò)誤做法
$cookie = Yii::$app->request->cookies->get('user_name');
$cookie->expire = 60 * 60 * 24; // 更新cookie過(guò)期時(shí)間
Yii::$app->response->cookies->add($cookie);
// 這種做法也會(huì)存在兩個(gè)同名的cookie
// 一個(gè)domain為: api.mike.com; 一個(gè)為: mike.com
//正確做法
$cookie = Yii::$app->request->cookies->get('user_name');
$cookie->expire = 60 * 60 * 24; // 更新cookie過(guò)期時(shí)間
$cookie->domain = 'mike.com';
Yii::$app->response->cookies->add($cookie);
需要注意的是Yii2從request里面獲取的cookie對(duì)象只有name,value屬性值,如果domain不是當(dāng)前域嫁艇,那么需要重新指定