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

嘿,各位代码爱好者,欢迎来到今天的 WordPress 源码剖析小课堂。今天我们要扒一扒 WordPress 的一个核心函数:is_user_logged_in()。别看它名字平平无奇,它可是 WordPress 判断用户是否登录的关键人物,直接关系到网站的用户体验和安全性。

咱们先来简单回顾一下登录机制:用户输入用户名和密码,服务器验证成功后,会在用户的浏览器上设置一个 Cookie,这个 Cookie 就像一张通行证,证明用户已经通过身份验证。下次用户访问网站时,浏览器会自动携带这个 Cookie,服务器通过检查 Cookie 来判断用户的登录状态,免去了每次都输入密码的麻烦。

is_user_logged_in() 函数就是这个登录机制在 WordPress 代码中的具体体现。它主要通过检查 Cookie 和全局变量来判断用户是否已登录。 接下来,我们就深入源码,看看它到底是怎么工作的。

1. 函数的庐山真面目

首先,让我们看看 is_user_logged_in() 函数的源码 (位于 wp-includes/pluggable.php 文件中):

/**
 * Determines whether the current visitor is a logged in user.
 *
 * @since 2.0.0
 *
 * @return bool True if user is logged in, false if not.
 */
function is_user_logged_in() {
    /** @global WP_User $current_user */
    global $current_user;

    return ! empty( $current_user->ID );
}

是不是感觉有点简单? 别着急,魔鬼藏在细节里。 它主要做了两件事:

  1. 声明全局变量 $current_user: global $current_user; 这行代码告诉 PHP,我们要使用全局范围内的 $current_user 变量。这个变量存储了当前登录用户的相关信息。
  2. 判断 $current_user->ID 是否为空: return ! empty( $current_user->ID ); 这行代码是判断用户是否登录的关键。如果 $current_user 对象存在,并且它的 ID 属性(用户的唯一标识符)不为空,那么就认为用户已经登录,函数返回 true;否则,返回 false

2. $current_user 是从哪里来的?

现在问题来了,$current_user 这个全局变量是从哪里来的?它又是如何被赋值的呢? 这才是整个登录判断的核心。

在 WordPress 中,用户登录状态的初始化和 $current_user 变量的赋值主要发生在 wp() 函数中。这个函数位于 wp-includes/functions.php 文件中,是 WordPress 启动流程中的一个重要环节。

让我们来简化一下 wp() 函数的相关代码,以便更好地理解:

function wp() {
    global $wp, $wp_query, $wp_the_query, $wp_rewrite, $wp_did_header, $wp_locale, $l10n, $did_action,
           $current_user;

    // ... 省略一些代码 ...

    $wp->init(); // 初始化 WordPress

    // ... 省略一些代码 ...

    $wp->parse_request(); // 解析请求

    // ... 省略一些代码 ...

    $wp->query_posts(); // 执行查询

    // ... 省略一些代码 ...

    $wp->register_globals(); // 注册全局变量

    // ... 省略一些代码 ...

    // Set the current user.
    $current_user = wp_get_current_user();

    // ... 省略一些代码 ...
}

可以看到,在 wp() 函数中,通过 $current_user = wp_get_current_user(); 这行代码来设置 $current_user 变量。 接下来,我们深入 wp_get_current_user() 函数,看看它是如何工作的。

3. wp_get_current_user() 函数的秘密

wp_get_current_user() 函数 (位于 wp-includes/pluggable.php 文件中) 负责从 Cookie 或者会话 (Session,如果启用了) 中获取用户的信息,并填充 $current_user 变量。

/**
 * Retrieves the current user object.
 *
 * @since 2.0.0
 *
 * @return WP_User Current user's WP_User object.
 */
function wp_get_current_user() {
    global $current_user;

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

    /**
     * Fires after the current user is set, but before any authentication cookies are checked.
     *
     * @since 2.1.0
     */
    do_action( 'set_current_user' );

    if ( ! is_user_logged_in() ) {
        wp_set_current_user( 0 );
    }

    return $current_user;
}

这个函数做了以下几件事:

  1. 初始化 $current_user: 如果 $current_user 变量还不是 WP_User 类的实例,那么就创建一个新的 WP_User 对象。
  2. 执行 set_current_user 动作: do_action( 'set_current_user' ); 这行代码允许其他插件或主题在用户对象设置之前执行一些操作,比如自定义身份验证。
  3. 调用 wp_set_current_user(): wp_set_current_user( 0 ); 这行代码看起来有点奇怪,传入的参数是 0。 实际上,这个函数的目的是根据 Cookie 或者其他方式来设置用户的信息。 如果没有找到已登录的用户,wp_set_current_user(0) 会将 $current_user 设置为一个游客用户。

4. wp_set_current_user() 函数:Cookie 大作战

wp_set_current_user() 函数 (位于 wp-includes/pluggable.php 文件中) 是真正根据 Cookie 来设置用户信息的关键。 它会读取 Cookie 中的用户 ID 和认证令牌,然后验证这些信息是否有效。

/**
 * Sets the current user.
 *
 * @since 2.5.0
 *
 * @param int|WP_User $id User ID or WP_User object.
 */
function wp_set_current_user( $id = 0 ) {
    global $current_user;

    if ( is_object( $id ) && isset( $id->ID ) ) {
        $id = $id->ID;
    }

    $id = (int) $id;

    if ( empty( $id ) ) {
        $current_user = new WP_User( 0 );
        return;
    }

    $current_user = get_userdata( $id );
}

简化后的代码:

function wp_set_current_user( $id = 0 ) {
    global $current_user;

    $id = (int) $id;

    if ( $id ) {
        $current_user = get_userdata( $id );
    } else {
        $current_user = new WP_User( 0 );
    }
}

这里主要做的事情:

  1. 类型转换和判断: 首先,将传入的 $id 参数转换为整数类型。 如果 $id 为 0,则创建一个游客用户(WP_User 对象,ID 为 0)。
  2. 通过 get_userdata() 获取用户信息: 如果 $id 不为 0,那么就调用 get_userdata( $id ) 函数来获取用户信息。

那么这个get_userdata() 又是如何运作的呢? 继续往下看。

5. get_userdata() 函数:从数据库中捞人

get_userdata() 函数(位于 wp-includes/user.php 文件中)负责从数据库中根据用户 ID 获取用户的详细信息。

/**
 * Retrieve user info by user ID.
 *
 * @since 2.0.0
 *
 * @param int $user_id User ID
 *
 * @return WP_User|false WP_User object on success, false on failure.
 */
function get_userdata( $user_id ) {
    $user_id = (int) $user_id;
    if ( ! $user_id ) {
        return false;
    }

    $_user = WP_User::get_instance( $user_id );

    if ( ! $_user ) {
        return false;
    }

    return $_user;
}

简化后的代码:

function get_userdata( $user_id ) {
    $user_id = (int) $user_id;

    if ( ! $user_id ) {
        return false;
    }

    $_user = WP_User::get_instance( $user_id );

    if ( ! $_user ) {
        return false;
    }

    return $_user;
}

这个函数首先将 $user_id 转换为整数,然后调用 WP_User::get_instance() 方法来获取 WP_User 对象。 如果找不到对应的用户,则返回 false

6. WP_User::get_instance():用户对象的制造者

WP_User::get_instance() 方法(位于 wp-includes/class-wp-user.php 文件中) 是 WP_User 类的静态方法,负责创建或从缓存中获取 WP_User 对象。它才是真正连接 Cookie 与数据库,验证登录状态的关键。 它会读取 Cookie 中的信息,并与数据库中的数据进行比对,确保用户的身份是合法的。

虽然源码比较长,但是我们专注于登录验证相关的部分。为了便于理解,这里我们只关注与 Cookie 和验证相关的逻辑,并进行简化:

/**
 * Retrieve user data from the database by user ID.
 *
 * @since 3.4.0
 *
 * @param int $id User ID.
 * @return WP_User|false WP_User object on success, false on failure.
 */
public static function get_instance( $id = 0 ) {
    // ... 省略缓存相关的代码 ...

    $user = new WP_User( $id );

    if ( $user->exists() ) {
      // ...省略其他逻辑...

      if ( ! is_user_logged_in() ) {
        wp_validate_auth_cookie();
      }
    }

    // ... 省略缓存相关的代码 ...

    return ! empty( $user->data ) ? $user : false;
}

可以看到,如果用户存在 ( $user->exists() 返回 true ),并且当前用户未登录 ( ! is_user_logged_in() 返回 true ), 那么就会调用 wp_validate_auth_cookie() 函数来验证用户的身份。

7. wp_validate_auth_cookie():Cookie 验证官

wp_validate_auth_cookie() 函数(位于 wp-includes/pluggable.php 文件中) 负责验证 Cookie 中的认证信息,确保用户没有被篡改身份。它会读取 Cookie 中的用户名、密码哈希,然后与数据库中的数据进行比对。

/**
 * Validates authentication cookie.
 *
 * @since 2.5.0
 *
 * @param string $cookie  Optional. Authentication cookie. Default is to grab from $_COOKIE.
 * @param string $scheme  Optional. Scheme to use. Default is 'auth'. Accepts 'auth', 'secure_auth', or 'logged_in'
 * @return int|false User ID if the cookie is validated, false otherwise.
 */
function wp_validate_auth_cookie( $cookie = '', $scheme = '' ) {
    if ( ! $cookie ) {
        if ( ! empty( $_COOKIE[ LOGGED_IN_COOKIE ] ) ) {
            $cookie = $_COOKIE[ LOGGED_IN_COOKIE ];
            $scheme = 'logged_in';
        } elseif ( ! empty( $_COOKIE[ SECURE_AUTH_COOKIE ] ) ) {
            $cookie = $_COOKIE[ SECURE_AUTH_COOKIE ];
            $scheme = 'secure_auth';
        } else {
            return false;
        }
    }

    $cookie_elements = explode( '|', $cookie );
    if ( count( $cookie_elements ) !== 4 ) {
        return false;
    }

    list( $username, $expiration, $token, $hmac ) = $cookie_elements;

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

    if ( $expiration < time() ) {
        return false;
    }

    $user = get_user_by( 'login', $username );
    if ( ! $user ) {
        return false;
    }

    $token_from_db = get_user_meta( $user->ID, 'session_tokens', true );
    if ( ! is_array( $token_from_db ) ) {
        return false;
    }

    $found_token = false;
    foreach ( $token_from_db as $session_hash => $session_data ) {
        if ( hash_equals( $session_hash, wp_hash( $token, 'auth_session' ) ) ) {
            $found_token = true;
            break;
        }
    }

    if ( ! $found_token ) {
        return false;
    }

    wp_set_current_user( $user->ID );

    /**
     * Fires after the authentication cookie is validated.
     *
     * @since 2.5.0
     *
     * @param int    $user_id User ID.
     * @param string $scheme  Authentication scheme.
     */
    do_action( 'auth_cookie_validated', $user->ID, $scheme );

    return $user->ID;
}

简单梳理一下:

  1. 获取 Cookie: 函数首先尝试从 $_COOKIE 数组中获取登录 Cookie。WordPress 使用 LOGGED_IN_COOKIESECURE_AUTH_COOKIE 这两个常量来定义 Cookie 的名称。 SECURE_AUTH_COOKIE 用于 HTTPS 连接,而 LOGGED_IN_COOKIE 用于普通的 HTTP 连接。
  2. 解析 Cookie: 如果找到了 Cookie,函数会将 Cookie 的值按照 | 分隔符拆分成多个部分,包括用户名、过期时间、token 和 HMAC(哈希消息认证码)。
  3. 验证 HMAC: HMAC 用于验证 Cookie 的完整性,防止 Cookie 被篡改。 函数会使用 wp_hash() 函数生成一个密钥,然后使用这个密钥和 Cookie 中的其他信息计算出一个 HMAC 值,并与 Cookie 中的 HMAC 值进行比较。 如果两个 HMAC 值不相等,那么就认为 Cookie 被篡改,验证失败。
  4. 验证过期时间: 函数会检查 Cookie 的过期时间,如果已经过期,那么就认为 Cookie 无效,验证失败。
  5. 根据用户名获取用户: 函数会使用 get_user_by( 'login', $username ) 函数根据用户名从数据库中获取用户的信息。
  6. 验证 Token: 从数据库中获取用户的会话 token,并与Cookie中的token进行比对。
  7. 设置当前用户: 如果 Cookie 验证通过,函数会调用 wp_set_current_user( $user->ID ) 函数将当前用户设置为登录状态。
  8. 触发 auth_cookie_validated 动作: do_action( 'auth_cookie_validated', $user->ID, $scheme ); 这行代码允许其他插件或主题在 Cookie 验证通过后执行一些操作。
  9. 返回用户 ID: 函数返回用户的 ID,表示 Cookie 验证成功。

8. 总结:is_user_logged_in() 的工作流程

现在,我们把整个流程串起来,总结一下 is_user_logged_in() 函数的工作原理:

  1. 调用 is_user_logged_in(): 当 WordPress 需要判断用户是否登录时,会调用 is_user_logged_in() 函数。
  2. 检查 $current_user->ID: is_user_logged_in() 函数会检查全局变量 $current_userID 属性是否为空。 如果不为空,就认为用户已经登录,返回 true
  3. 初始化 $current_user (如果需要): 如果 $current_user 变量还没有被初始化,或者 $current_user->ID 为空,那么 WordPress 会调用 wp_get_current_user() 函数来尝试获取用户信息。
  4. 读取 Cookie: wp_get_current_user() 函数会调用 wp_set_current_user() 函数,wp_set_current_user() 函数会尝试从 Cookie 中读取用户 ID 和认证信息。
  5. 验证 Cookie: wp_set_current_user() 函数会调用 wp_validate_auth_cookie() 函数来验证 Cookie 的有效性。 wp_validate_auth_cookie() 函数会检查 Cookie 的完整性、过期时间,并与数据库中的数据进行比对。
  6. 设置 $current_user: 如果 Cookie 验证通过,wp_set_current_user() 函数会根据 Cookie 中的用户 ID 从数据库中获取用户的详细信息,并将这些信息存储到 $current_user 变量中。
  7. 返回登录状态: is_user_logged_in() 函数最终会根据 $current_user->ID 是否为空来返回用户的登录状态。

为了更清晰的展示整个流程,我们用表格来总结一下:

函数 主要功能 依赖的 Cookie
is_user_logged_in() 判断用户是否登录,通过检查 $current_user->ID 是否为空。
wp_get_current_user() 获取当前用户对象,如果 $current_user 不存在,则尝试从 Cookie 中获取用户信息。
wp_set_current_user() 根据用户 ID 设置当前用户对象,如果用户 ID 为 0,则创建一个游客用户。
wp_validate_auth_cookie() 验证 Cookie 的有效性,包括检查 Cookie 的完整性、过期时间,并与数据库中的数据进行比对。 如果验证通过,则设置当前用户为登录状态。 LOGGED_IN_COOKIESECURE_AUTH_COOKIE
get_userdata() 根据用户 ID 从数据库中获取用户的详细信息。
WP_User::get_instance() 创建或从缓存中获取 WP_User 对象。

9. 安全性考量

WordPress 的登录机制依赖于 Cookie,因此安全性非常重要。 为了防止 Cookie 被窃取或篡改,WordPress 采取了以下措施:

  • HTTPS: 建议使用 HTTPS 连接,因为 HTTPS 可以加密 Cookie,防止 Cookie 在传输过程中被窃取。
  • HMAC: 使用 HMAC 来验证 Cookie 的完整性,防止 Cookie 被篡改。
  • 过期时间: 设置 Cookie 的过期时间,防止 Cookie 被长期使用。
  • HTTPOnly: 设置 Cookie 的 HTTPOnly 属性,防止 JavaScript 访问 Cookie,从而防止 XSS 攻击。
  • Secure: 对于 HTTPS 连接,设置 Cookie 的 Secure 属性,确保 Cookie 只能通过 HTTPS 连接传输。
  • Token: 使用会话token来验证登录,防止cookie被重放。

10. 总结

is_user_logged_in() 函数是 WordPress 登录机制的核心,它通过检查全局变量 $current_user 和验证 Cookie 来判断用户的登录状态。 深入理解 is_user_logged_in() 函数的工作原理,可以帮助我们更好地理解 WordPress 的登录机制,从而更好地开发和维护 WordPress 网站。

希望今天的课程对大家有所帮助! 记住,阅读源码是提升编程技能的有效途径之一。 多看、多想、多动手,你也能成为 WordPress 专家! 下次再见!

发表回复

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