移动端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
相关产品推荐
相关产品推荐

