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 请求,在这种情况下,竞争条件是一个合理的场景:

  1. 第一个请求开始(使用您的初始 PHPSESSID)
  2. 第二个请求开始(再次使用相同的 PHPSESSID)
  3. 第一个请求已处理并收到带有新 PHPSESSID 的响应
  4. 第二个请求直到现在才被阻止(session 处理程序使用锁定来防止多个进程同时修改相同的数据)并且刚刚 开始处理初始 PHPSESSID,此时无效,因此会触发 log-out.

我会亲自查看由 onLoad 事件触发的内容 - 很容易将所有初始化逻辑放在那里而忘记这可能包括多个异步请求。


无论哪种方式,您真正的逻辑错误是这段代码:

if ( SESSION_REGENERATE_ID )
    session_regenerate_id(SESSION_REGENERATE_ID);

您在两种不同的条件下使用相同的值:

  1. 确定是否重新生成session ID
  2. 告知 session_regenerate_id() 是否应立即销毁与旧 session ID 关联的数据

选项 not do destroy that data immediately 正是为了解决这些异步请求的竞争条件,因为它们实际上是不可避免的。竞争条件 在某个时刻发生,无论您如何努力避免它 - 即使没有逻辑缺陷,网络延迟(例如)仍可能触发它。
保留旧 session 的数据(当然是暂时的)通过简单地允许 "late" 或 "out of sync" 请求处理启动时可用的任何数据来解决该问题。

过期的 session 稍后将由 session 垃圾收集器清理。这可能并不理想,但几乎是需要您删除数据的存储的唯一解决方案(与 Redis 等缓存存储相反,它允许您设置 TTL 值,而不必手动删除)。

就我个人而言,我更愿意避免 session ID 重新生成,特别是在 AJAX 请求期间……如您所见,这是一堆蠕虫。 :)