PHP带“记住我”功能的安全登录API实现安全问题咨询
解决Token跨设备窃取的实用方案
针对你遇到的Cookie Token被他人窃取后跨设备登录的问题,以下是几个可落地的PHP后端解决方案:
1. 绑定设备特征+IP段(而非完整IP)
不要直接绑定完整IP,而是结合用户代理(User-Agent)哈希和IP前两段(IPv4)/IP前缀(IPv6)生成设备标识,和Token一起存入数据库。验证时不仅校验Token有效性,还要比对当前请求的设备标识与数据库记录是否一致:
// 生成Token时创建设备标识 $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $ip = $_SERVER['REMOTE_ADDR'] ?? ''; // 提取IP前两段(适配IPv4,IPv6可取前32位前缀) $ipSegment = implode('.', array_slice(explode('.', $ip), 0, 2)); $deviceFingerprint = hash('sha256', $userAgent . $ipSegment); // 将Token与设备标识关联存入数据库 $stmt = $pdo->prepare("INSERT INTO user_tokens (user_id, token, device_fingerprint, expires_at) VALUES (?, ?, ?, ?)"); $stmt->execute([$userId, $token, $deviceFingerprint, date('Y-m-d H:i:s', time() + 7*86400)]); // 验证Token时校验设备标识 $stmt = $pdo->prepare("SELECT * FROM user_tokens WHERE token = ? AND expires_at > NOW()"); $stmt->execute([$token]); $tokenRecord = $stmt->fetch(PDO::FETCH_ASSOC); if ($tokenRecord) { $currentUserAgent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $currentIp = $_SERVER['REMOTE_ADDR'] ?? ''; $currentIpSegment = implode('.', array_slice(explode('.', $currentIp), 0, 2)); $currentFingerprint = hash('sha256', $currentUserAgent . $currentIpSegment); if ($currentFingerprint !== $tokenRecord['device_fingerprint']) { http_response_code(401); exit('设备验证失败,请重新登录'); } }
这种方式既避免了动态IP的问题,又大幅提高了跨设备复用Token的门槛。
2. 强化Cookie安全属性
给Token Cookie设置HttpOnly、Secure、SameSite属性,从传输和读取层面减少窃取风险:
setcookie('auth_token', $token, [ 'expires' => time() + 7*86400, 'path' => '/', 'domain' => 'your-domain.com', 'secure' => true, // 仅在HTTPS连接下传输 'httponly' => true, // 禁止前端JS读取Cookie,防范XSS窃取 'samesite' => 'Strict' // 严格限制跨站请求携带Cookie,降低CSRF和跨设备复用可能 ]);
3. 实现Token定期刷新+单Token失效机制
给Token设置较短的有效期(比如7天),每次用户产生活跃行为(如访问页面、提交操作)时,后端生成新Token,替换数据库中的旧Token并更新Cookie。这样即使Token被窃取,有效期内用户一旦活跃,旧Token就会失效,窃取者无法继续使用。
4. 双Token(Access Token + Refresh Token)架构
- Access Token:有效期短(15-30分钟),用于接口鉴权,可存在内存或临时Cookie中;
- Refresh Token:有效期长(7-30天),存在
HttpOnlyCookie中,仅用于过期后获取新的Access Token和Refresh Token。 - 关键规则:Refresh Token只能使用一次,每次刷新后旧Refresh Token立即失效,生成新的存入数据库。
示例代码(刷新Token逻辑):
// 接收Refresh Token $refreshToken = $_COOKIE['refresh_token'] ?? ''; // 验证Refresh Token $stmt = $pdo->prepare("SELECT * FROM user_refresh_tokens WHERE token = ? AND expires_at > NOW()"); $stmt->execute([$refreshToken]); $refreshRecord = $stmt->fetch(PDO::FETCH_ASSOC); if ($refreshRecord) { // 生成新的Access Token和Refresh Token $newAccessToken = bin2hex(random_bytes(32)); $newRefreshToken = bin2hex(random_bytes(64)); // 失效旧Refresh Token $stmt = $pdo->prepare("UPDATE user_refresh_tokens SET expires_at = NOW() WHERE id = ?"); $stmt->execute([$refreshRecord['id']]); // 存入新Refresh Token $stmt = $pdo->prepare("INSERT INTO user_refresh_tokens (user_id, token, expires_at) VALUES (?, ?, ?)"); $stmt->execute([$refreshRecord['user_id'], $newRefreshToken, date('Y-m-d H:i:s', time() + 7*86400)]); // 返回新Access Token,更新Refresh Token Cookie setcookie('refresh_token', $newRefreshToken, [ 'expires' => time() + 7*86400, 'path' => '/', 'domain' => 'your-domain.com', 'secure' => true, 'httponly' => true, 'samesite' => 'Strict' ]); echo json_encode(['access_token' => $newAccessToken]); } else { http_response_code(401); exit('请重新登录'); }
5. 异常登录提醒+主动下线
当Token验证发现设备标识或IP段差异较大时,后端触发邮件/短信提醒用户,并提供主动下线所有异常设备的功能(清空该用户所有有效Token)。
内容的提问来源于stack exchange,提问作者Shivam Shukla
相关产品推荐
相关产品推荐

