【问题标题】:getJSON and session_regenerate_id()getJSON 和 session_regenerate_id()
【发布时间】:2016-05-01 19:07:52
【问题描述】:

我正在从受会话保护的页面执行标准 getJSON 查询:

$.getJSON('queries.php',{q: 'updateEvent', param1: p1},
    function(data){
        ...
    }
);

在我的会话构造函数中,我设置了以下内容:

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 请求的标头中。 答案中返回的 PHPSESSID 与 session_regenerate_id 的预期不同。

如果 SESSION_REGENERATE_ID 设置为 FALSE,则请求将毫无问题地通过。如果设置为 TRUE,则会收到错误消息“登录失败”。

这里是 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:这是完整的 ASSession 代码:

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:这里是请求标头和响应 cookie:

我注意到在onload 期间执行的第一个getJSON 是成功的。在用户之后完成并由用户触发的所有其他操作均未成功

【问题讨论】:

  • 我理解您在我的回答中的评论(我现在删除它,如果我能为您找到任何其他解决方案,将取消删除)。那么,您的代币实际上是如何工作的呢?如果会话数据存在,那么它只会改变 session_id,所有其他的应该保持不变。为什么它发送错误的令牌?它只是将 session_id 作为令牌发送吗?
  • 是的,只结束 session_id。我读过 getJSON 默认情况下不发送凭据。这就是为什么 ajax 有一个名为 withCredentials 的 xhrField 应该设置为 true。但我不知道如何将它包含在 getJSON 中,因为官方文档中没有专门包含 xhrFields 的语法
  • 更具体地说,php 页面返回一条错误消息,指出无法登录(因此无法识别会话数据)。在控制台中,我看到请求标头 set-cookie 正在发送一个 PHPSESSID,但错误消息附带的响应 PHPSESSID 不同
  • 如果这样可以解决您的问题,只需使用$.ajax 函数并使用额外参数dataType:"json" 使其像$.getJSON 一样工作。像这样$.ajax({url:url,xhrFields: {withCredentials: true }, dataType:"json"}); 告诉我,如果这对你有用。
  • 这只是一个$.getJSON() 呼叫还是多个?而究竟是什么时候这些调用被执行了?在页面加载期间或之后?如果有多个调用,它们是否同时执行并且它们全部都失败了?请发布相关的请求和响应标头。

标签: php jquery session getjson


【解决方案1】:

这主要是由竞争条件引起的,但也可能是浏览器错误。

排除浏览器错误情况,但提供的信息中存在冲突,更具体地说是this comment

这是多个调用,根据用户操作一一进行,绝不会同时进行。

如果请求从未同时执行,那只能说明您的浏览器无法正常运行,并且会发生以下情况之一:

  • 丢弃它在响应中收到的Set-Cookie 标头(如果该逻辑取决于HttpOnly 标志,这将解释为什么网络仍然有效:D)
  • onLoad 事件实际上是在页面加载期间执行的(我知道这没有意义,但如果是浏览器错误,一切皆有可能)

当然,这些不太可能发生,所以我倾向于说您实际上一次处理多个 AJAX 请求,在这种情况下,竞争条件是一个合理的场景:

  1. 第一个请求开始(使用您的初始 PHPSESSID)
  2. 第二个请求开始(同样,使用相同的 PHPSESSID)
  3. 第一个请求得到处理并接收到带有新 PHPSESSID 的响应
  4. 第二个请求被阻塞到现在(会话处理程序使用锁定来防止多个进程同时修改相同的数据)并且刚刚开始使用初始 PHPSESSID,此时无效,因此会触发注销。

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


不管怎样,你真正的逻辑错误是这段代码:

if ( SESSION_REGENERATE_ID )
    session_regenerate_id(SESSION_REGENERATE_ID);

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

  1. 确定是否完全重新生成会话 ID
  2. 告诉session_regenerate_id() 是否应该立即销毁与旧会话 ID 关联的数据

选项 not 确实会立即破坏该数据,因为它们实际上是不可避免的,因此可以通过异步请求为这些竞争条件提供解决方案。竞争条件会在某个时候发生,无论您如何努力避免它 - 即使没有逻辑缺陷,网络延迟(例如)仍可能触发它。
保留旧会话的数据(当然是暂时的)通过简单地允许“延迟”或“不同步”请求处理启动时可用的任何数据来解决该问题。

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

就我个人而言,我更喜欢在 AJAX 请求期间避免会话 ID 重新生成……如您所见,这是一罐蠕虫。 :)

【讨论】:

  • 非常感谢您的回答!非常详细和实用!肯定没有浏览器错误。竞争条件很可能会发生。您的回答让我明白了为什么最初的 Ajax 请求成功以及其他请求失败的原因。正如之前在 cmets 中所解释的,session_regenerate_id 不会破坏会话数据,只会更改 PHPSESSID。因此,出于好奇,有没有办法,如果我们同时/或在关闭时间发生多个 Ajax 请求,以使它们成功....用于将会话重新生成为某个计时器...例如 1s,所以所有请求都有时间完成?
  • “正如之前在 cmets 中所解释的,session_regenerate_id 不会破坏会话数据,只会更改 PHPSESSID” - 问题是......数据会被新的会话 ID 携带,但它是一个 复制旧数据。当您调用session_regenerate_id(true) 时,旧副本被销毁,这使会话无效。只需将其更改为 false 并让垃圾收集器继续工作。 :)
  • 是的,大多数 JS 框架(包括 jQuery)发送带有 ajax 请求的 X-Requested-With: XMLHttpRequest 标头 - 你可以检查一下。但要小心 - 这也允许任何人防止在每个请求上重新生成会话 ID,因此您还需要实现在一定时间后不惜一切代价重新生成的逻辑。
  • 没有。 :) 您可以在自己的屏幕截图中看到这个(以及 X-Requested-With 标头)。
  • 它显示了一个:scheme: 行,并且您的cookie 带有secure 标志,这意味着如果它不是通过https 传输的,您甚至都看不到它。
猜你喜欢
  • 2015-10-14
  • 2019-04-09
  • 2018-04-07
  • 1970-01-01
  • 1970-01-01
  • 2011-05-06
  • 2011-05-31
  • 2014-11-17
  • 1970-01-01
相关资源
最近更新 更多