getJSON 和 session_regenerate_id()
getJSON and session_regenerate_id()
我正在从受 session 保护的页面执行标准 getJSON 查询:
$.getJSON('queries.php',{q: 'updateEvent', param1: p1},
function(data){
...
}
);
在我的 session 构造函数中,我设置了以下内容:
function startSession()
{
ini_set('session.use_only_cookies', SESSION_USE_ONLY_COOKIES);
$cookieParams = session_get_cookie_params();
session_set_cookie_params(
$cookieParams["lifetime"],
$cookieParams["path"],
$cookieParams["domain"],
SESSION_SECURE,
SESSION_HTTP_ONLY
);
session_start();
if ( SESSION_REGENERATE_ID )
session_regenerate_id(SESSION_REGENERATE_ID);
}
如果我将 SESSION_REGENERATE_ID
设置为 true,那么我的 getJSON 会发送一个令牌,但会收到一个不同的令牌,从而导致请求失败。所以目前我正在处理 SESSION_REGENERATE_ID
设置为 false.
有没有办法让 getJSON 在这种情况下工作?
编辑:所有文件都在同一个域下。
我们有 index.php 其中包含 js,我们有 queries.php 这是 ajax 请求调用的 php 文件,我们有 s_session.php 其中包括上面编写的构造函数。
文件 index.html 和 queries.php 都在开始时以这种方式受到保护:
include "s_session.php";
if(!$login->isLoggedIn()) {
header('Content-Type: application/json');
echo json_encode(array('content' => 'Login failed'));
exit;
}
PHPSESSID 在 set-cookie 下的 ajax 请求的 header 中。
答案中返回的 PHPSESSID 不同,正如 session_regenerate_id.
所预期的那样
如果 SESSION_REGENERATE_ID 设置为 FALSE,请求将顺利通过。如果它设置为 TRUE,那么我会收到错误消息 "Login failed"。
这里是 isLoggedIn() :
public function isLoggedIn() {
//if $_SESSION['user_id'] is not set return false
if(ASSession::get("user_id") == null)
return false;
//if enabled, check fingerprint
if(LOGIN_FINGERPRINT == true) {
$loginString = $this->_generateLoginString();
$currentString = ASSession::get("login_fingerprint");
if($currentString != null && $currentString == $loginString)
return true;
else {
//destroy session, it is probably stolen by someone
$this->logout();
return false;
}
}
$user = new ASUser(ASSession::get("user_id"));
return $user->getInfo() !== null;
}
编辑 2:这是完整的评估代码:
class ASSession {
/**
* Start session.
*/
public static function startSession()
{
ini_set('session.use_only_cookies', SESSION_USE_ONLY_COOKIES);
session_start();
$s = $_SESSION;
$cookieParams = session_get_cookie_params();
session_set_cookie_params(
$cookieParams["lifetime"],
$cookieParams["path"],
$cookieParams["domain"],
SESSION_SECURE,
SESSION_HTTP_ONLY
);
if ( SESSION_REGENERATE_ID )
session_regenerate_id(SESSION_REGENERATE_ID);
//$_SESSION = $s;
}
/**
* Destroy session.
*/
public static function destroySession() {
$_SESSION = array();
$params = session_get_cookie_params();
setcookie( session_name(),
'',
time() - 42000,
$params["path"],
$params["domain"],
$params["secure"],
$params["httponly"]
);
session_destroy();
}
/**
* Set session data.
* @param mixed $key Key that will be used to store value.
* @param mixed $value Value that will be stored.
*/
public static function set($key, $value) {
$_SESSION[$key] = $value;
}
/**
* Unset session data with provided key.
* @param $key
*/
public static function destroy($key) {
if ( isset($_SESSION[$key]) )
unset($_SESSION[$key]);
}
/**
* Get data from $_SESSION variable.
* @param mixed $key Key used to get data from session.
* @param mixed $default This will be returned if there is no record inside
* session for given key.
* @return mixed Session value for given key.
*/
public static function get($key, $default = null) {
if(isset($_SESSION[$key]))
return $_SESSION[$key];
else
return $default;
}
}
编辑 3:这里是请求 headers 和响应 cookie:
我注意到在 onload
期间执行的第一个 getJSON 是成功的。在用户之后完成并由用户触发的所有其他操作均不成功
这主要是由竞争条件引起的,但也有可能是浏览器错误。
我 会 排除浏览器错误情况,但提供的信息存在冲突,更具体地说 this comment:
It is several calls, made one by one on user action, never simultaneously.
如果请求从未同时执行,那么这只能意味着您的浏览器运行不正常,并且出现以下情况之一发生:
- 丢弃它在响应中收到的
Set-Cookie
header(如果该逻辑取决于 HttpOnly
标志,这将解释为什么网络仍然有效 :D)
onLoad
事件实际上是在页面加载期间执行的(我知道这没有意义,但如果是浏览器错误,一切皆有可能)
当然,这些不太可能发生,所以我倾向于说你实际上是在一次处理多个 AJAX 请求,在这种情况下,竞争条件是一个合理的场景:
- 第一个请求开始(使用您的初始 PHPSESSID)
- 第二个请求开始(再次使用相同的 PHPSESSID)
- 第一个请求已处理并收到带有新 PHPSESSID 的响应
- 第二个请求直到现在才被阻止(session 处理程序使用锁定来防止多个进程同时修改相同的数据)并且刚刚 开始处理初始 PHPSESSID,此时无效,因此会触发 log-out.
我会亲自查看由 onLoad
事件触发的内容 - 很容易将所有初始化逻辑放在那里而忘记这可能包括多个异步请求。
无论哪种方式,您真正的逻辑错误是这段代码:
if ( SESSION_REGENERATE_ID )
session_regenerate_id(SESSION_REGENERATE_ID);
您在两种不同的条件下使用相同的值:
- 确定是否重新生成session ID
- 告知
session_regenerate_id()
是否应立即销毁与旧 session ID 关联的数据
选项 not do destroy that data immediately 正是为了解决这些异步请求的竞争条件,因为它们实际上是不可避免的。竞争条件 将 在某个时刻发生,无论您如何努力避免它 - 即使没有逻辑缺陷,网络延迟(例如)仍可能触发它。
保留旧 session 的数据(当然是暂时的)通过简单地允许 "late" 或 "out of sync" 请求处理启动时可用的任何数据来解决该问题。
过期的 session 稍后将由 session 垃圾收集器清理。这可能并不理想,但几乎是需要您删除数据的存储的唯一解决方案(与 Redis 等缓存存储相反,它允许您设置 TTL 值,而不必手动删除)。
就我个人而言,我更愿意避免 session ID 重新生成,特别是在 AJAX 请求期间……如您所见,这是一堆蠕虫。 :)
我正在从受 session 保护的页面执行标准 getJSON 查询:
$.getJSON('queries.php',{q: 'updateEvent', param1: p1},
function(data){
...
}
);
在我的 session 构造函数中,我设置了以下内容:
function startSession()
{
ini_set('session.use_only_cookies', SESSION_USE_ONLY_COOKIES);
$cookieParams = session_get_cookie_params();
session_set_cookie_params(
$cookieParams["lifetime"],
$cookieParams["path"],
$cookieParams["domain"],
SESSION_SECURE,
SESSION_HTTP_ONLY
);
session_start();
if ( SESSION_REGENERATE_ID )
session_regenerate_id(SESSION_REGENERATE_ID);
}
如果我将 SESSION_REGENERATE_ID
设置为 true,那么我的 getJSON 会发送一个令牌,但会收到一个不同的令牌,从而导致请求失败。所以目前我正在处理 SESSION_REGENERATE_ID
设置为 false.
有没有办法让 getJSON 在这种情况下工作?
编辑:所有文件都在同一个域下。
我们有 index.php 其中包含 js,我们有 queries.php 这是 ajax 请求调用的 php 文件,我们有 s_session.php 其中包括上面编写的构造函数。
文件 index.html 和 queries.php 都在开始时以这种方式受到保护:
include "s_session.php";
if(!$login->isLoggedIn()) {
header('Content-Type: application/json');
echo json_encode(array('content' => 'Login failed'));
exit;
}
PHPSESSID 在 set-cookie 下的 ajax 请求的 header 中。 答案中返回的 PHPSESSID 不同,正如 session_regenerate_id.
所预期的那样如果 SESSION_REGENERATE_ID 设置为 FALSE,请求将顺利通过。如果它设置为 TRUE,那么我会收到错误消息 "Login failed"。
这里是 isLoggedIn() :
public function isLoggedIn() {
//if $_SESSION['user_id'] is not set return false
if(ASSession::get("user_id") == null)
return false;
//if enabled, check fingerprint
if(LOGIN_FINGERPRINT == true) {
$loginString = $this->_generateLoginString();
$currentString = ASSession::get("login_fingerprint");
if($currentString != null && $currentString == $loginString)
return true;
else {
//destroy session, it is probably stolen by someone
$this->logout();
return false;
}
}
$user = new ASUser(ASSession::get("user_id"));
return $user->getInfo() !== null;
}
编辑 2:这是完整的评估代码:
class ASSession {
/**
* Start session.
*/
public static function startSession()
{
ini_set('session.use_only_cookies', SESSION_USE_ONLY_COOKIES);
session_start();
$s = $_SESSION;
$cookieParams = session_get_cookie_params();
session_set_cookie_params(
$cookieParams["lifetime"],
$cookieParams["path"],
$cookieParams["domain"],
SESSION_SECURE,
SESSION_HTTP_ONLY
);
if ( SESSION_REGENERATE_ID )
session_regenerate_id(SESSION_REGENERATE_ID);
//$_SESSION = $s;
}
/**
* Destroy session.
*/
public static function destroySession() {
$_SESSION = array();
$params = session_get_cookie_params();
setcookie( session_name(),
'',
time() - 42000,
$params["path"],
$params["domain"],
$params["secure"],
$params["httponly"]
);
session_destroy();
}
/**
* Set session data.
* @param mixed $key Key that will be used to store value.
* @param mixed $value Value that will be stored.
*/
public static function set($key, $value) {
$_SESSION[$key] = $value;
}
/**
* Unset session data with provided key.
* @param $key
*/
public static function destroy($key) {
if ( isset($_SESSION[$key]) )
unset($_SESSION[$key]);
}
/**
* Get data from $_SESSION variable.
* @param mixed $key Key used to get data from session.
* @param mixed $default This will be returned if there is no record inside
* session for given key.
* @return mixed Session value for given key.
*/
public static function get($key, $default = null) {
if(isset($_SESSION[$key]))
return $_SESSION[$key];
else
return $default;
}
}
编辑 3:这里是请求 headers 和响应 cookie:
我注意到在 onload
期间执行的第一个 getJSON 是成功的。在用户之后完成并由用户触发的所有其他操作均不成功
这主要是由竞争条件引起的,但也有可能是浏览器错误。
我 会 排除浏览器错误情况,但提供的信息存在冲突,更具体地说 this comment:
It is several calls, made one by one on user action, never simultaneously.
如果请求从未同时执行,那么这只能意味着您的浏览器运行不正常,并且出现以下情况之一发生:
- 丢弃它在响应中收到的
Set-Cookie
header(如果该逻辑取决于HttpOnly
标志,这将解释为什么网络仍然有效 :D) onLoad
事件实际上是在页面加载期间执行的(我知道这没有意义,但如果是浏览器错误,一切皆有可能)
当然,这些不太可能发生,所以我倾向于说你实际上是在一次处理多个 AJAX 请求,在这种情况下,竞争条件是一个合理的场景:
- 第一个请求开始(使用您的初始 PHPSESSID)
- 第二个请求开始(再次使用相同的 PHPSESSID)
- 第一个请求已处理并收到带有新 PHPSESSID 的响应
- 第二个请求直到现在才被阻止(session 处理程序使用锁定来防止多个进程同时修改相同的数据)并且刚刚 开始处理初始 PHPSESSID,此时无效,因此会触发 log-out.
我会亲自查看由 onLoad
事件触发的内容 - 很容易将所有初始化逻辑放在那里而忘记这可能包括多个异步请求。
无论哪种方式,您真正的逻辑错误是这段代码:
if ( SESSION_REGENERATE_ID )
session_regenerate_id(SESSION_REGENERATE_ID);
您在两种不同的条件下使用相同的值:
- 确定是否重新生成session ID
- 告知
session_regenerate_id()
是否应立即销毁与旧 session ID 关联的数据
选项 not do destroy that data immediately 正是为了解决这些异步请求的竞争条件,因为它们实际上是不可避免的。竞争条件 将 在某个时刻发生,无论您如何努力避免它 - 即使没有逻辑缺陷,网络延迟(例如)仍可能触发它。
保留旧 session 的数据(当然是暂时的)通过简单地允许 "late" 或 "out of sync" 请求处理启动时可用的任何数据来解决该问题。
过期的 session 稍后将由 session 垃圾收集器清理。这可能并不理想,但几乎是需要您删除数据的存储的唯一解决方案(与 Redis 等缓存存储相反,它允许您设置 TTL 值,而不必手动删除)。
就我个人而言,我更愿意避免 session ID 重新生成,特别是在 AJAX 请求期间……如您所见,这是一堆蠕虫。 :)