深入理解 WordPress `is_user_logged_in()` 函数的源码:如何检查 `Cookie` 和全局变量来判断登录状态。

嘿,大家好!我是你们今天的 WordPress 登录状态侦察兵,今天咱们来聊聊 WordPress 里那个神出鬼没的 is_user_logged_in() 函数,看看它到底是怎么判断你是不是已经登录了。准备好了吗?咱们这就开讲!

一、初探 is_user_logged_in():门卫的微笑

首先,咱们得搞清楚,is_user_logged_in() 这家伙是干嘛的?简单来说,它就是 WordPress 网站的门卫,负责告诉你:“嘿,朋友,你登录了吗?” 如果你已经登录,它会笑眯眯地返回 true,否则就冷冰冰地返回 false

这个函数在 WordPress 的各种场景下都非常有用,比如:

  • 控制内容显示: 只有登录用户才能看到某些特定内容。
  • 限制操作权限: 只有登录用户才能执行某些操作,比如发表评论、修改资料等。
  • 个性化用户体验: 根据用户登录状态,显示不同的界面或功能。

二、源码剖析:Cookie 的秘密

想要彻底理解 is_user_logged_in(),我们就得扒开它的源码,看看它到底用了什么魔法。 打开 wp-includes/pluggable.php 文件,你会找到下面这段代码(简化版):

function is_user_logged_in() {
    $user = wp_get_current_user();
    return ( $user->exists() );
}

嗯?就这么简单?别急,戏肉还在后头。is_user_logged_in() 只是个幌子,它真正的工作都交给了 wp_get_current_user()。 咱们继续追踪 wp_get_current_user() 的源码(也在 wp-includes/pluggable.php 中):

function wp_get_current_user() {
    static $current_user = null;

    if ( ! isset( $current_user ) ) {
        $current_user = wp_get_current_user_data();
    }

    return $current_user;
}

这里引入了一个静态变量 $current_user。这是个优化技巧,避免每次调用 wp_get_current_user() 都重新获取用户信息。如果 $current_user 还没有被赋值,就调用 wp_get_current_user_data() 来获取用户信息。 关键就在 wp_get_current_user_data() 函数! 咱们再继续追踪 wp_get_current_user_data() 的源码(仍然在 wp-includes/pluggable.php 中,但为了展示方便,我们假设它是一个单独的函数):

function wp_get_current_user_data() {
    global $current_user, $wp_roles;

    if ( ! ( $current_user instanceof WP_User ) ) {
        $current_user = new WP_User();
    }

    if ( defined( 'XMLRPC_REQUEST' ) || defined( 'REST_REQUEST' ) || ( defined( 'WP_INSTALLING' ) && WP_INSTALLING ) || ! apply_filters( 'determine_current_user', false ) ) {
        return $current_user;
    }

    // Get data on anonymous user.
    get_currentuserinfo();

    // If the user exists, fill in the data.
    if ( 0 < $current_user->ID ) {
        wp_cache_set( $current_user->ID, $current_user, 'users' );
    } else {
        $current_user->caps = array();
        $current_user->roles = array();
    }

    return $current_user;
}

这里涉及到了全局变量 $current_user$wp_roles。首先,如果 $current_user 不是 WP_User 类的实例,就创建一个新的 WP_User 对象。

接下来,有一堆条件判断,如果满足其中任何一个,就直接返回 $current_user(匿名用户)。这些条件包括:

  • XMLRPC_REQUEST:正在处理 XML-RPC 请求。
  • REST_REQUEST:正在处理 REST API 请求。
  • WP_INSTALLING:正在安装 WordPress。
  • apply_filters( 'determine_current_user', false ):有插件通过 determine_current_user 过滤器阻止获取用户信息。

如果以上条件都不满足,就调用 get_currentuserinfo() 函数来获取用户信息。这就是真正判断登录状态的地方!

三、深入 get_currentuserinfo():Cookie 的解读艺术

get_currentuserinfo() 函数才是真正的幕后英雄,它负责从 Cookie 中读取用户信息,并填充到 $current_user 对象中。 咱们继续追踪 get_currentuserinfo() 的源码(也在 wp-includes/pluggable.php 中,同样假设它是一个单独的函数):

function get_currentuserinfo() {
    global $current_user, $wpdb;

    if ( ! isset( $_COOKIE[AUTH_COOKIE] ) && ! isset( $_COOKIE[SECURE_AUTH_COOKIE] ) && ! isset( $_COOKIE[LOGGED_IN_COOKIE] ) ) {
        return;
    }

    $auth_cookie_name = '';
    $auth_cookie      = '';

    if ( isset( $_COOKIE[SECURE_AUTH_COOKIE] ) && is_ssl() ) {
        $auth_cookie_name = SECURE_AUTH_COOKIE;
        $auth_cookie      = $_COOKIE[SECURE_AUTH_COOKIE];
    } elseif ( isset( $_COOKIE[AUTH_COOKIE] ) ) {
        $auth_cookie_name = AUTH_COOKIE;
        $auth_cookie      = $_COOKIE[AUTH_COOKIE];
    } else {
        $auth_cookie_name = LOGGED_IN_COOKIE;
        $auth_cookie      = $_COOKIE[LOGGED_IN_COOKIE];
    }

    if ( empty( $auth_cookie ) ) {
        return;
    }

    list( $username, $expiration, $token, $hmac ) = wp_parse_auth_cookie( $auth_cookie, 'logged_in' );

    if ( ! hash_equals( $hmac, hash_hmac( 'md5', $username . '|' . $expiration . '|' . $token, wp_hash( $username . $expiration . $token, 'auth' ) ) ) ) {
        return;
    }

    $user = get_user_by( 'login', $username );

    if ( ! $user ) {
        return;
    }

    $current_user = $user;

    wp_cache_set( $current_user->ID, $current_user, 'users' );
}

这段代码做了以下几件事:

  1. 检查 Cookie: 首先,它会检查是否存在以下 Cookie:

    • AUTH_COOKIE:标准认证 Cookie。
    • SECURE_AUTH_COOKIE:安全认证 Cookie(仅在 HTTPS 连接下使用)。
    • LOGGED_IN_COOKIE:登录状态 Cookie。

    如果这三个 Cookie 都不存在,说明用户没有登录,直接返回。

  2. 选择 Cookie: 根据 HTTPS 连接状态,选择合适的 Cookie。 如果是 HTTPS 连接,优先使用 SECURE_AUTH_COOKIE,否则使用 AUTH_COOKIE。 如果以上两种 Cookie 都不存在,就使用 LOGGED_IN_COOKIE

  3. 解析 Cookie: 调用 wp_parse_auth_cookie() 函数解析 Cookie 的值。 wp_parse_auth_cookie() 函数会将 Cookie 的值分解成以下几个部分:

    • username:用户名。
    • expiration:过期时间戳。
    • token:安全令牌。
    • hmac:哈希消息认证码,用于验证 Cookie 的真实性。
  4. 验证 HMAC: 使用 hash_hmac() 函数重新计算 HMAC,并与 Cookie 中存储的 HMAC 进行比较。 如果两者不一致,说明 Cookie 被篡改,直接返回。

  5. 获取用户信息: 调用 get_user_by( 'login', $username ) 函数根据用户名从数据库中获取用户信息。

  6. 设置全局变量: 将获取到的用户信息赋值给全局变量 $current_user

  7. 缓存用户信息: 将用户信息缓存到 WordPress 对象缓存中,方便下次使用。

四、Cookie 的构成:一场精心设计的加密游戏

我们来深入了解一下 WordPress 的 Cookie 到底长什么样,以及它是如何防止被篡改的。

Cookie 名称 作用
AUTH_COOKIE 存储用户名、过期时间、令牌和 HMAC,用于在非 HTTPS 连接下验证用户身份。
SECURE_AUTH_COOKIE AUTH_COOKIE 类似,但仅在 HTTPS 连接下使用,提供更高的安全性。
LOGGED_IN_COOKIE 存储用户名、过期时间、令牌和 HMAC,用于记住用户的登录状态,即使关闭浏览器后也能自动登录。
wordpress_test_cookie 用于检测浏览器是否启用了 Cookie。
wp-settings-{user_id} 存储用户的个性化设置,例如编辑器模式、界面颜色等。 {user_id} 是用户的 ID。
wp-settings-time-{user_id} 存储 wp-settings-{user_id} Cookie 的过期时间。 {user_id} 是用户的 ID。

其中,AUTH_COOKIESECURE_AUTH_COOKIELOGGED_IN_COOKIE 是最关键的,它们都包含以下信息:

  • 用户名: 用户的登录名。
  • 过期时间戳: Cookie 的过期时间,是一个 Unix 时间戳。
  • 安全令牌: 一个随机生成的字符串,用于增加 Cookie 的安全性。
  • HMAC: 哈希消息认证码,用于验证 Cookie 的真实性,防止被篡改。

HMAC 的计算方式如下:

HMAC = hash_hmac( 'md5', username . '|' . expiration . '|' . token, wp_hash( username . expiration . token, 'auth' ) )
  • hash_hmac():PHP 的 HMAC 函数,使用 MD5 算法。
  • usernameexpirationtoken:Cookie 中存储的用户名、过期时间和安全令牌。
  • wp_hash():WordPress 的哈希函数,用于生成一个唯一的密钥。
  • 'auth':哈希密钥的盐值,用于增加哈希的安全性。

这种设计保证了即使有人获取了 Cookie 的值,也无法轻易篡改它,因为篡改后的 Cookie 的 HMAC 值会与原始值不一致,从而导致验证失败。

五、全局变量 $current_user:用户信息的中转站

通过以上的分析,我们可以看到,is_user_logged_in() 函数最终依赖于全局变量 $current_user。 那么,$current_user 是在哪里定义的呢?

实际上,$current_user 是在 WordPress 的核心文件中定义的,通常在 wp-settings.php 文件中。

global $wpdb, $wp_locale, $l10n, $tinymce_version, $required_php_version, $required_mysql_version, $_wp_submenu_nopriv, $_wp_real_parent_file,
       $_wp_menu_nopriv, $menu, $submenu, $_registered_pages, $_parent_pages, $pagenow, $wp_importers, $plugin_page, $hook_suffix, $allowedposttags,
       $allowedtags, $wp_taxonomies, $wp_post_types, $wp_scripts, $wp_styles, $wp_meta_boxes, $shortcode_tags, $wp, $id, $comment, $post,
       $authordata, $current_screen, $wp_list_table, $wp_widget_factory, $wp_roles, $current_user;

可以看到,$current_user 只是众多全局变量中的一个。 当用户登录成功后,get_currentuserinfo() 函数会将用户信息填充到 $current_user 对象中。 之后,我们就可以通过 $current_user 对象访问用户的各种信息,例如:

  • $current_user->ID:用户 ID。
  • $current_user->user_login:用户名。
  • $current_user->user_email:用户邮箱。
  • $current_user->roles:用户角色。
  • $current_user->caps:用户权限。

六、is_user_logged_in() 的应用场景:让网站更智能

现在,我们已经了解了 is_user_logged_in() 函数的原理,那么它在实际开发中有什么应用呢?

1. 控制内容显示

if ( is_user_logged_in() ) {
    // 显示登录用户才能看到的内容
    echo '<p>欢迎回来,' . wp_get_current_user()->display_name . '!</p>';
    echo '<a href="' . wp_logout_url() . '">退出登录</a>';
} else {
    // 显示未登录用户才能看到的内容
    echo '<p>请先<a href="' . wp_login_url() . '">登录</a>或<a href="' . wp_registration_url() . '">注册</a>。</p>';
}

2. 限制操作权限

if ( is_user_logged_in() && current_user_can( 'edit_posts' ) ) {
    // 显示编辑文章的链接
    echo '<a href="' . admin_url( 'post-new.php' ) . '">新建文章</a>';
}

3. 个性化用户体验

if ( is_user_logged_in() ) {
    // 根据用户的角色,显示不同的界面
    $user = wp_get_current_user();
    if ( in_array( 'administrator', (array) $user->roles ) ) {
        // 管理员界面
        echo '<p>欢迎进入管理员控制面板!</p>';
    } else {
        // 普通用户界面
        echo '<p>欢迎来到您的个人中心!</p>';
    }
}

七、总结:Cookie、全局变量和登录状态的三角恋

总结一下,is_user_logged_in() 函数的判断流程可以概括为以下几点:

  1. is_user_logged_in() 调用 wp_get_current_user()
  2. wp_get_current_user() 调用 wp_get_current_user_data()
  3. wp_get_current_user_data() 调用 get_currentuserinfo()
  4. get_currentuserinfo() 从 Cookie 中读取用户信息,并填充到全局变量 $current_user 中。
  5. is_user_logged_in() 函数最终通过判断 $current_user 对象是否存在,来确定用户是否已经登录。

可以用一个表格来表示它们的关系:

函数 作用
is_user_logged_in() 判断用户是否已经登录。
wp_get_current_user() 获取当前用户信息,使用了静态变量缓存用户信息,避免重复查询。
wp_get_current_user_data() 检查是否存在特殊请求(如 XML-RPC、REST API 或安装过程),如果是,则返回匿名用户。否则,调用 get_currentuserinfo() 从 Cookie 中获取用户信息。
get_currentuserinfo() 从 Cookie 中读取用户信息,验证 Cookie 的真实性,并填充全局变量 $current_user
全局变量 $current_user 存储当前用户信息,是判断用户登录状态的关键。
Cookie 存储用户的认证信息,包括用户名、过期时间、安全令牌和 HMAC。

它们之间的关系就像一场三角恋:

  • Cookie 是爱情信物,记录着用户的登录信息。
  • 全局变量 $current_user 是爱情的结晶,存储着用户的个人资料。
  • is_user_logged_in() 是爱情的见证人,判断用户是否已经坠入爱河(登录)。

好了,今天的 WordPress 登录状态侦察课就到这里。希望通过今天的学习,大家能够更加深入地理解 is_user_logged_in() 函数的原理,并在实际开发中灵活运用它,让你的 WordPress 网站更加智能、安全和个性化。 下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注