You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

移动端Remember Me记住登录功能随机异常登出问题排查

问题描述
  • 早期参考网络教程实现简易登录系统,用户点击「Remember Me」按钮后,系统会在当前登录设备的Cookie中存储member_login、random_password、random_selector三类数据,该版本仅在PC端使用时无异常。
  • 后续为支持多设备同时登录、适配移动端访问需求,新增IP地址校验步骤。目前PC端登录功能运行正常,勾选Remember Me后可保持1个月登录态;但移动端设备上所有浏览器都会在随机时长后自动触发登出。
  • 已确认移动端Cookie可正常创建,不存在生成异常,需定位问题根因及排查方向。
相关实现代码

登录流程Cookie设置代码

<?php
require_once ($_SERVER['DOCUMENT_ROOT'] . '/_inc/functions.php');
require_once ($_SERVER['DOCUMENT_ROOT'] . '/_inc/auth/Util.php');
require_once ($_SERVER['DOCUMENT_ROOT'] . '/_inc/auth/Auth.php');

$auth = new Auth();
$db_handle = new DBController();
$util = new Util();

// Get Current date, time
$current_time = time();
$current_date = date("Y-m-d H:i:s", $current_time);

// Set Cookie expiration for 1 month (seconds from 1970 until current date + 1 month)
$cookie_expiration_time = $current_time + (30 * 24 * 60 * 60);  // for 1 month

// Auth.php chcek if user is loggedin
if ($_SESSION["user_id"]) {
    redirect_page("index.php");
    exit;
}

// check if login form was submitted
if (! empty($_POST['login'])) {

  $isAuthenticated = false;

  // get username and password from form
  $username = $_POST['username'];
  $password = $_POST['password'];

  // get user from db
  $user = $auth->getMemberByUsername($username);

  // verify entered password with hashed password in db for user got above
  if (password_verify($password, $user[0]["password"])) {
      $isAuthenticated = true; // password is verified, next rocess of login can start
  }

  // if user is authenticated start to create cookies
  if ($isAuthenticated) {
      $_SESSION["user_id"] = $user[0]["id"];

      // Set Auth Cookies if 'Remember Me' checked
      if (! empty($_POST["remember"])) {

          $ip_address = $_SERVER['REMOTE_ADDR'];

          // setcookie(string $name, string $value = "", int $expires = 0,)
          setcookie("member_login", $username, $cookie_expiration_time, '/'); // '/' cookies are available on each page

          $random_password = $util->getToken(16); // create token for cookie identification with db
          setcookie("random_password", $random_password, $cookie_expiration_time, '/'); // '/' cookies are available on each page

          $random_selector = $util->getToken(32);
          setcookie("random_selector", $random_selector, $cookie_expiration_time, '/'); // '/' cookies are available on each page

          // hash password and selector before inserting to db
          $random_password_hash = password_hash($random_password, PASSWORD_DEFAULT);
          $random_selector_hash = password_hash($random_selector, PASSWORD_DEFAULT);

          $expiry_date = date("Y-m-d H:i:s", $cookie_expiration_time);

          // mark existing token as expired if new login
          $userToken = $auth->getTokenByUsername($username, 0);
          /*
          if (! empty($userToken[0]["id"])) {
              $auth->markAsExpired($userToken[0]["id"]);
          }
          */
          // Insert new token
          $auth->insertToken($username, $ip_address, $random_password_hash, $random_selector_hash, $expiry_date);

      } else {
          $util->clearAuthCookie();
      }

      redirect_page("index.php");
      exit;

    } else {
        $_SESSION['login_error'] = 'Invalid password or username';
        redirect_page("back");
        exit();
    }
}

全局页面加载Cookie校验代码

<?php

/* FLow:
-> index -> header -> validatecookies ()
            -> continue index (logedin = true)
            -> redirect login
 */

require 'Util.php';
require 'Auth.php';

// create objects
$auth = new Auth();
$db_handle = new DBController();
$util = new Util();

$isLoggedIn = false;

// Check if loggedin session and redirect if session exists
if (! empty($_SESSION["user_id"])) {
    $isLoggedIn = true;
}
// Check if loggedin cookies exists
else if (! empty($_COOKIE["member_login"]) && ! empty($_COOKIE["random_password"]) && ! empty($_COOKIE["random_selector"])) {
    // Initiate auth token verification directive to false
    $isPasswordVerified = false;
    $isSelectorVerified = false;
    $isExpiryDateVerified = false;

    // Get token for username from db
    $userToken = $auth->getTokenByIPaddress($_SERVER['REMOTE_ADDR'],0);
    // $userToken = $auth->getTokenByUsername($_COOKIE["member_login"],0);

    if ($userToken) {
    // check just in case of the same IP address

        // dual control via selector and password due to time leake secure issue (if just one token than according to response time from db it is possible to guess password easier)

        // Validate random password cookie with database
        if (password_verify($_COOKIE["random_password"], $userToken[0]["password_hash"])) {
            $isPasswordVerified = true;
        }

        // Validate random selector cookie with database
        if (password_verify($_COOKIE["random_selector"], $userToken[0]["selector_hash"])) {
            $isSelectorVerified = true;
        }

        // check cookie expiration by date
        if( ($userToken[0]["expiry_date"] >= $current_date) & ($userToken[0]["is_expired"] != 1) )  {
            $isExpiryDateVerified = true;
        }
    }
    // Redirect if all cookie based validation returns true
    // Else, mark the token as expired and clear cookies
    if (!empty($userToken[0]["id"]) && $isPasswordVerified && $isSelectorVerified && $isExpiryDateVerified) {
       $isLoggedIn = true;

    } else {

        if(!empty($userToken[0]["id"])) {
            $auth->markAsExpired($userToken[0]["id"]);
        }
        // clear cookies
        $util->clearAuthCookie();
        header ("Location: login.php");
        exit;
    }

} else {
// is no session and no cookies exist, just redirect on login

    header ("Location: login.php");

    exit;
}

?>
根因定位与修复方向

核心问题出在IP校验逻辑的设计缺陷,这是移动端随机登出的直接原因:

  • 移动端网络环境和PC固定宽带差异极大,蜂窝网络下基站切换、WiFi/蜂窝网络切换、运营商IP池动态调度都会导致设备公网IP频繁随机变动。你当前的校验逻辑是直接用请求来源IP去数据库匹配登录时存储的token,一旦IP变动就查不到对应记录,直接判定登录失效清除Cookie,自然会出现随机登出。PC端公网IP租期长、变动概率极低,所以不会触发该问题。

除此之外代码中还有几处会导致登录异常的问题,需要同步修复:

  • 查询逻辑错误:你注释掉了原本按用户名查询token的逻辑,改为仅按IP查询token。同一IP下如果有多个用户登录,只会返回第一条token记录,根本无法匹配当前用户的Cookie验证串,会出现大面积的误判登出。正确逻辑应该是先通过Cookie中的member_login字段查询对应用户名下所有未过期的token,再逐一匹配selector、密码哈希、IP信息完成校验。
  • Cookie参数不全:调用setcookie()时未设置SameSite、Secure、domain参数,移动端浏览器对Cookie安全策略限制比PC更严格,缺少这些参数可能导致Cookie在部分场景下被浏览器拦截,无法正常携带到请求中。
  • 变量未定义:校验逻辑中用于判断过期时间的$current_date变量没有在该文件中初始化,会导致过期判断逻辑运行异常。
  • 运算符误用:过期判断时使用了位运算符&而非逻辑与运算符&&,极端场景下会出现判断结果不符合预期的问题。

内容的提问来源于stack exchange,提问作者Dump

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 04:57:22