让代码更简单

WordPress用户cookie生成算法

重要:本文最后更新于2025-03-13 21:15:29,某些文章具有时效性,若有错误或已失效,请在下方留言或联系代码狗

这是几年前就写下的草稿,直到这两天才有空研究wordpress的cookie生成算法。当时想实现前后端分离,即wordpress作为后端管理数据,前端则使用MVC模型实现。为了能够做到脱离wordpress登录,而不破坏数据库,所以必须知道wordpress的登录cookie逻辑。这样做还有一个好处,那就是如果自己写的前端有问题,可以随时切换回wordpress主题。

WordPress用户cookie生成算法,文件目录:wp-includes/pluggable.php

复制
       $user = get_userdata( $user_id );
if ( ! $user ) {
return '';
}
if ( ! $token ) {
$manager = WP_Session_Tokens::get_instance( $user_id );
$token = $manager->create( $expiration );
}

$pass_frag = substr( $user->user_pass, 8, 4 );

$key = wp_hash( $user->user_login . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );

// If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
$algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
$hash = hash_hmac( $algo, $user->user_login . '|' . $expiration . '|' . $token, $key );

$cookie = $user->user_login . '|' . $expiration . '|' . $token . '|' . $hash;

比较复杂,直接看我如何实现的吧

复制
<?php
class WordPressLogin {
    private $logged_in_key = LOGGED_IN_KEY.LOGGED_IN_SALT;
    private $auth_key = AUTH_KEY.AUTH_SALT;
    private $site_url = '你的网站首页地址'; // 站点 URL
    private $cookie_expiration = 3600 * 24 * 7; // 7天有效
    private $pdo; // 数据库连接对象

    // 构造函数 - 连接数据库
    public function __construct($db) {
        $this->pdo = $db;
    }

    // 计算 WordPress 哈希
    private function wp_hash($data, $scheme = 'auth') {
        $salt = ($scheme === 'auth') ? $this->auth_key : $this->logged_in_key;
        return hash_hmac('md5', $data, $salt);
    }

    // 生成 WordPress 兼容 Session Token
    private function generateSessionToken() {
        return substr(strtr(base64_encode(random_bytes(32)), '+/', '-_'), 0, 43);
    }

    // 获取用户信息(从数据库中查询)
    private function getUserByUsername($username) {
        $stmt = $this->pdo->prepare("SELECT ID, user_pass FROM wp_users WHERE user_login = ?");
        $stmt->execute([$username]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    // 验证密码是否匹配
    private function verifyPassword($password, $hashed_password) {
        return password_verify($password, $hashed_password);
    }

    // 存储 session_tokens 到 wp_usermeta
  private function saveSessionToken($user_id, $token, $expiration) {
    try {
        $token_key = hash('sha256', $token);

        // 查询现有的 session_tokens
        $stmt = $this->pdo->prepare("SELECT meta_value FROM wp_usermeta WHERE user_id = ? AND meta_key = 'session_tokens'");
        $stmt->execute([$user_id]);
        $existing_tokens = $stmt->fetchColumn();

        // 解析 session_tokens(如果存在)
        $session_tokens = $existing_tokens ? unserialize($existing_tokens) : [];

        // **清理过期 session**
        $current_time = time();
        $session_tokens = array_filter($session_tokens, function ($session) use ($current_time) {
            return $session['expiration'] >= $current_time;
        });

        // **限制最多存储 2 个 session**
        if (count($session_tokens) >= 2) {
            array_shift($session_tokens); // 移除最早的 session
        }

        // **添加新 session**
        $session_tokens[$token_key] = [
            'expiration' => $expiration,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
            'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'login' => $current_time
        ];

        // **序列化 session_tokens**
        $serialized_tokens = serialize($session_tokens);

        // **先尝试 UPDATE**
        $stmt = $this->pdo->prepare("UPDATE wp_usermeta SET meta_value = ? WHERE user_id = ? AND meta_key = 'session_tokens'");
        $stmt->execute([$serialized_tokens, $user_id]);

        // **如果 UPDATE 没有更新任何行,则执行 INSERT**
        if ($stmt->rowCount() === 0) {
            $stmt = $this->pdo->prepare("INSERT INTO wp_usermeta (user_id, meta_key, meta_value) VALUES (?, 'session_tokens', ?)");
            $stmt->execute([$user_id, $serialized_tokens]);
        }

    } catch (PDOException $e) {
        error_log("Session token 保存失败: " . $e->getMessage());
    }
}

    // 用户登录并生成 Cookies

    public function loginCookies($mpath, $cookiename, $username, $password, $scheme = 'auth') {
        $user = $this->getUserByUsername($username);
        if (!$user) return false;
        require_once 'class-PasswordHash.php';
        $wp_hasher = new PasswordHash(8, true);
        if (!$wp_hasher->CheckPassword($password, $user['user_pass'])) {
            return false;
        }

        $user_id = $user['ID'];
        $expiration = time() + $this->cookie_expiration;
        $token = $this->generateSessionToken();
        $this->saveSessionToken($user_id, $token, $expiration);
        $pass_frag = substr($user['user_pass'], 8, 4);
        $key = $this->wp_hash($username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme);
        $hash = hash_hmac('sha256', $username . '|' . $expiration . '|' . $token, $key);
        //var_dump($hash);
        $cookie_value = $username . '|' . $expiration . '|' . $token . '|' . $hash;
        $cookie_name = $cookiename . md5($this->site_url);

        $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? true : false;
        $domain = parse_url($this->site_url, PHP_URL_HOST);
        $httponly = true;
        setcookie($cookie_name, $cookie_value, $expiration, $mpath, $domain, $secure, $httponly);
        return true;
    }

}

上面的代码中,使用到了wordpress配置中的常量数据,就是wordpress网站根目录下的wp-config.php文件,里面有一串密钥,用来生成加密数据的,请不要泄露请使用你自己的,否则会验证不通过,每个wordpress站点中这些值都不一样,直接复制是无效的。

复制
define( 'AUTH_KEY',         '^b_mluTc%G]*v}B~kF*~;D*Q 5)^:Aij4!{?ri{)r$GNQ%,30ab @+5S[Joq#n3.' );
define( 'SECURE_AUTH_KEY',  '?TzNvI&^Gs@bU&>a^eHiP+O23c_%^8Sh`>)3x;][/M~vr+U4`m~&h13$*t|#Bm:?' );
define( 'LOGGED_IN_KEY',    '`^i3,i+~VAXM7Dh,j_BZ:E$aZ.`8wzGb*INftQ,>A:cC8h`jxQ(re B&IuCzxN(F' );
define( 'NONCE_KEY',        '/gwrUkWsVkHXfz&S-[/m9X>=`Za9i)ZF!q5X:2VYi6r*T;ASRe_u2VrrGd-S%I!!' );
define( 'AUTH_SALT',        'hk*K2C_4]imWDqSGQv&HU{|~H4-^nb/<WG+ccA>2AHOaj6o&qoVAeky^tGZt47P8' );
define( 'SECURE_AUTH_SALT', '6OD,OZjq0*1u)t9jwFdrEY?,x&K.9q1.OkDi3-pWxajlZ?7FrIoT<[V<:%H&4^MW' );
define( 'LOGGED_IN_SALT',   'wP53jhWI@)?#(N<!Dv031LUsgTRgn(]03KoByEQ>fRjL-in}QQOs?F?y|h2v3T$ ' );
define( 'NONCE_SALT',       '.-D}B6%K#g_loZ8{xcF`Js2.=.phEt%2h+!oZ2WPV.m-}D[x1>Pl!;Gt.eY!B??g' );

这里介绍下常量数据,后端授权cookie使用AUTH_KEY+AUTH_SALT,如果是HTTPS网站,则使用SECURE_AUTH_KEY+SECURE_AUTH_SALT,前端登录使用LOGGED_IN_KEY+LOGGED_IN_SALT

使用到的class-PasswordHash.php是密码校验类,用于检查密码是否和数据库存储的内容一致,详情请看狗哥以前的文章:WordPress用户密码加密及验证详解

调用loginCookies方法,就会查询数据库,校验用户,校验通过,就使用加密后的密码的4个字符生成cookie信息。调用此方法后,可以切换到wordpress环境,访问网站,看看是否已经被wordpress识别为登录状态。

感觉很棒!可以赞赏支持我哟~

0 打赏

评论 (0)

登录后评论
QQ咨询 邮件咨询 狗哥推荐