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

如何在PHP中实现仅允许已认证设备登录的系统?

PHP实现仅允许已认证设备登录的方案(无Session/过期Cookie)

核心问题分析

你之前的代码仅通过User-Agent识别设备,无法区分配置完全相同的两台设备(比如同型号手机、同系统同浏览器的两台电脑),必须结合更多维度生成唯一设备指纹,再配合持久化的加密凭证实现无Session的设备认证。

解决方案步骤

1. 生成唯一设备指纹

除了User-Agent,还可以结合以下HTTP请求头信息(这些信息组合后,能大幅降低重复概率):

  • Accept-Language:语言偏好
  • Accept-Encoding:编码偏好
  • DNT:Do Not Track标识
  • Sec-Ch-Ua:Chrome系浏览器的UA信息(可选)
  • Sec-Ch-Ua-Mobile:移动端标识(可选)

将这些信息拼接后,通过哈希算法(比如SHA256)生成唯一指纹。

2. 持久化加密凭证

设置永不过期的Cookie,存储加密后的「用户ID+设备指纹」组合,服务器端通过密钥解密后验证合法性。

3. 设备认证流程

  • 用户首次登录(账号密码验证通过):生成当前设备的指纹,将其与用户ID绑定存入数据库,同时生成加密凭证写入Cookie
  • 后续请求:读取Cookie中的凭证,解密得到用户ID和设备指纹,检查该指纹是否存在于用户的已认证设备列表中,存在则允许访问

4. 设备管理功能

提供界面让用户查看所有已认证设备,支持移除指定设备(从数据库中删除对应指纹记录)

代码实现示例

1. 增强版设备检测与指纹生成类

class DeviceAuth
{
    // 生成设备唯一指纹
    public static function generateFingerprint()
    {
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        $acceptLang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';
        $acceptEnc = $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '';
        $dnt = $_SERVER['HTTP_DNT'] ?? '0';
        // 可选:Chrome系浏览器的额外标识
        $secUa = $_SERVER['HTTP_SEC_CH_UA'] ?? '';
        $secUaMobile = $_SERVER['HTTP_SEC_CH_UA_MOBILE'] ?? '';

        // 拼接所有维度信息
        $rawFingerprint = implode('|', [
            $userAgent,
            $acceptLang,
            $acceptEnc,
            $dnt,
            $secUa,
            $secUaMobile
        ]);

        // 生成SHA256哈希作为指纹
        return hash('sha256', $rawFingerprint);
    }

    // 原有的系统信息检测(保留并优化)
    public static function getSystemInfo()
    {
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        $osPlatform = "Unknown OS Platform";
        $osArray = [
            '/windows phone 8/i' => 'Windows Phone 8',
            '/windows phone os 7/i' => 'Windows Phone 7',
            '/windows nt 6.3/i' => 'Windows 8.1',
            '/windows nt 6.2/i' => 'Windows 8',
            '/windows nt 6.1/i' => 'Windows 7',
            '/windows nt 6.0/i' => 'Windows Vista',
            '/windows nt 5.2/i' => 'Windows Server 2003/XP x64',
            '/windows nt 5.1/i' => 'Windows XP',
            '/windows xp/i' => 'Windows XP',
            '/windows nt 5.0/i' => 'Windows 2000',
            '/windows me/i' => 'Windows ME',
            '/win98/i' => 'Windows 98',
            '/win95/i' => 'Windows 95',
            '/win16/i' => 'Windows 3.11',
            '/macintosh|mac os x/i' => 'Mac OS X',
            '/mac_powerpc/i' => 'Mac OS 9',
            '/linux/i' => 'Linux',
            '/ubuntu/i' => 'Ubuntu',
            '/iphone/i' => 'iPhone',
            '/ipod/i' => 'iPod',
            '/ipad/i' => 'iPad',
            '/android/i' => 'Android',
            '/blackberry/i' => 'BlackBerry',
            '/webos/i' => 'Mobile'
        ];

        $device = 'SYSTEM';
        foreach ($osArray as $regex => $value) {
            if (preg_match($regex, $userAgent)) {
                $osPlatform = $value;
                $device = preg_match('/(mobile|phone|ipad|ipod|iphone)/i', $osPlatform) ? 'MOBILE' : 'SYSTEM';
                break;
            }
        }

        return [
            'os' => $osPlatform,
            'device' => $device,
            'browser' => self::getBrowser()
        ];
    }

    // 原有的浏览器检测(优化)
    public static function getBrowser()
    {
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        $browser = "Unknown Browser";
        $browserArray = [
            '/msie/i' => 'Internet Explorer',
            '/firefox/i' => 'Firefox',
            '/safari/i' => 'Safari',
            '/chrome/i' => 'Chrome',
            '/opera/i' => 'Opera',
            '/netscape/i' => 'Netscape',
            '/maxthon/i' => 'Maxthon',
            '/konqueror/i' => 'Konqueror',
            '/mobile/i' => 'Handheld Browser'
        ];

        foreach ($browserArray as $regex => $value) {
            if (preg_match($regex, $userAgent)) {
                $browser = $value;
                break;
            }
        }
        return $browser;
    }

    // 加密凭证(使用AES-256-CBC)
    public static function encryptCredential($userId, $fingerprint, $key)
    {
        $iv = random_bytes(openssl_cipher_iv_length('aes-256-cbc'));
        $data = json_encode(['user_id' => $userId, 'fingerprint' => $fingerprint]);
        $encrypted = openssl_encrypt($data, 'aes-256-cbc', $key, 0, $iv);
        return base64_encode($encrypted . '::' . $iv);
    }

    // 解密凭证
    public static function decryptCredential($credential, $key)
    {
        $parts = explode('::', base64_decode($credential));
        if (count($parts) !== 2) {
            return false;
        }
        list($encryptedData, $iv) = $parts;
        $decrypted = openssl_decrypt($encryptedData, 'aes-256-cbc', $key, 0, $iv);
        return json_decode($decrypted, true);
    }
}

2. 数据库表设计(存储已认证设备)

CREATE TABLE user_authenticated_devices (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    fingerprint VARCHAR(64) NOT NULL, -- SHA256指纹长度为64
    device_info TEXT NOT NULL, -- 存储设备系统、浏览器信息(JSON格式)
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY idx_user_fingerprint (user_id, fingerprint)
);

3. 登录与认证逻辑示例

// 配置密钥(请使用随机生成的32位密钥,存储在环境变量中)
$ENCRYPT_KEY = getenv('DEVICE_AUTH_ENCRYPT_KEY'); // 比如:'your-32-character-secret-key-here'

// 1. 用户登录验证(账号密码正确后执行)
function handleLogin($userId) {
    global $ENCRYPT_KEY;
    // 生成当前设备指纹
    $fingerprint = DeviceAuth::generateFingerprint();
    // 获取设备信息
    $deviceInfo = json_encode(DeviceAuth::getSystemInfo());

    // 将设备指纹与用户绑定存入数据库
    $pdo = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass');
    $stmt = $pdo->prepare("INSERT IGNORE INTO user_authenticated_devices (user_id, fingerprint, device_info) VALUES (?, ?, ?)");
    $stmt->execute([$userId, $fingerprint, $deviceInfo]);

    // 生成加密凭证并设置永不过期的Cookie
    $credential = DeviceAuth::encryptCredential($userId, $fingerprint, $ENCRYPT_KEY);
    setcookie('device_auth', $credential, time() + 315360000, '/', '', false, true); // 10年有效期,HttpOnly
}

// 2. 请求时的设备认证验证
function validateDevice() {
    global $ENCRYPT_KEY;
    if (!isset($_COOKIE['device_auth'])) {
        return false; // 无凭证,需要登录
    }

    $credential = $_COOKIE['device_auth'];
    $decrypted = DeviceAuth::decryptCredential($credential, $ENCRYPT_KEY);
    if (!$decrypted || !isset($decrypted['user_id'], $decrypted['fingerprint'])) {
        return false; // 凭证无效
    }

    $userId = $decrypted['user_id'];
    $fingerprint = $decrypted['fingerprint'];

    // 检查数据库中是否存在该认证设备
    $pdo = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass');
    $stmt = $pdo->prepare("SELECT 1 FROM user_authenticated_devices WHERE user_id = ? AND fingerprint = ?");
    $stmt->execute([$userId, $fingerprint]);

    return $stmt->rowCount() > 0;
}

// 使用示例
if (!validateDevice()) {
    // 未通过设备认证,跳转到登录页
    header('Location: /login.php');
    exit;
}

// 已通过认证,继续处理请求

注意事项

  • 密钥安全性:加密密钥必须严格保密,建议存储在环境变量中,不要硬编码在代码里
  • 指纹唯一性:虽然组合多个HTTP头能大幅降低重复概率,但无法做到100%唯一(比如极端情况下两台设备的所有HTTP头完全一致),可以可选加入客户端生成的Canvas指纹(需要前端配合,通过AJAX发送给后端)
  • 隐私合规:如果使用Canvas等指纹技术,需要符合GDPR等隐私法规,告知用户并获得同意
  • Cookie安全:设置Cookie时开启HttpOnly和Secure(HTTPS环境下),防止XSS攻击

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 14:37:37