解析 WordPress `determine_current_user()` 函数的源码:如何根据 `Cookie` 和会话信息判断当前用户。

嘿,大家好!今天咱们来扒一扒 WordPress 里的一个关键函数——determine_current_user()。这哥们儿负责在茫茫人海(网络流量)中,识别出当前访问者是谁,也就是“你是谁,从哪里来,要到哪里去”的网络版。

咱们的目标是:深入源码,搞清楚它怎么靠 Cookie 和会话信息,判断出当前用户是谁。准备好了吗?Let’s dive in!

一、开场白:角色与职责

在WordPress世界里,用户身份验证是个核心环节。determine_current_user() 函数就像个门卫,每次有人来访,它都要检查访客的“身份证”(Cookie 或会话信息),然后决定是否放行,以及放行后赋予什么权限。

二、源码初探:在哪里,长什么样?

determine_current_user() 函数藏身于 wp-includes/pluggable.php 文件中。打开它,你可能会被密密麻麻的代码吓一跳。别怕,咱们一步步来。

function determine_current_user() {
    /** WordPress Hooks */
    do_action( 'determine_current_user' );

    global $current_user;

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

    // Get the current user ID from a cookie, if present.
    if ( ! empty( $_COOKIE[ LOGGED_IN_COOKIE ] ) ) {
        wp_set_current_user( wp_validate_auth_cookie() );
    } elseif ( ! empty( $_COOKIE[ SECURE_AUTH_COOKIE ] ) && is_ssl() ) {
        wp_set_current_user( wp_validate_auth_cookie( '', 'secure' ) );
    } elseif ( ! empty( $_COOKIE[ AUTH_COOKIE ] ) ) {
        wp_set_current_user( wp_validate_auth_cookie( '', 'auth' ) );
    }

    // If the user ID is still empty, try to get it from the session.
    if ( empty( $current_user->ID ) && is_user_logged_in() ) {
        $current_user = wp_get_current_user(); //This retrieves the user from the session
    }

    /** WordPress Hooks */
    do_action( 'after_determine_current_user' );
}

这段代码看起来有点长,但逻辑其实挺清晰的:

  1. 检查是否已经确定了用户: 如果 $current_user->ID 不为空,说明之前已经判断过了,直接返回。
  2. 从 Cookie 中获取用户 ID: 依次检查 LOGGED_IN_COOKIESECURE_AUTH_COOKIEAUTH_COOKIE 这三个 Cookie。如果存在,就调用 wp_validate_auth_cookie() 函数来验证 Cookie,并设置当前用户。
  3. 从会话中获取用户 ID: 如果 Cookie 没用,就检查会话。如果用户已经登录,就从会话中获取用户信息。
  4. 钩子: 在函数执行前后,分别调用 do_action() 函数,允许其他插件或主题扩展功能。

三、Cookie 侦探:wp_validate_auth_cookie() 详解

wp_validate_auth_cookie() 函数是 Cookie 验证的核心。它的作用是:

  • 解析 Cookie 的值。
  • 验证 Cookie 的有效性(比如,是否过期,是否被篡改)。
  • 如果 Cookie 有效,返回用户 ID;否则,返回 0。
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 ] ) && is_ssl() ) {
            $cookie = $_COOKIE[ SECURE_AUTH_COOKIE ];
            $scheme = 'secure';
        } elseif ( ! empty( $_COOKIE[ AUTH_COOKIE ] ) ) {
            $cookie = $_COOKIE[ AUTH_COOKIE ];
            $scheme = 'auth';
        } else {
            return false;
        }
    }

    $cookie_elements = explode( '|', $cookie );

    if ( count( $cookie_elements ) !== 4 ) {
        return false;
    }

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

    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
        $username = preg_replace( '/[^a-zA-Z0-9_.@-]/', '', $username );
        $expiration = preg_replace( '/[^0-9]/', '', $expiration );
    }

    /**
     * Filter the user data used to validate the auth cookie.
     *
     * @since 2.5.0
     *
     * @param array  $user_data Array of user data. Contains the username,
     *                          expiration, token, and the HMAC from the cookie.
     * @param string $cookie    The cookie value.
     * @param string $scheme    The scheme to use. 'auth', 'secure', 'logged_in'
     */
    $user_data = apply_filters( 'auth_cookie_validation', compact( 'username', 'expiration', 'token', 'hmac' ), $cookie, $scheme );

    $username   = $user_data['username'];
    $expiration = $user_data['expiration'];
    $token      = $user_data['token'];
    $hmac       = $user_data['hmac'];

    if ( empty( $username ) || ! is_numeric( $expiration ) ) {
        return false;
    }

    // Quick check to see if an honest cookie has expired.
    if ( $expiration < time() ) {
        return false;
    }

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

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

    $key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );
    $hash = hash_hmac( 'md5', $username . '|' . $expiration . '|' . $token, $key );

    if ( ! hash_equals( $hmac, $hash ) ) {
        /**
         * Fires after an invalid auth cookie is detected.
         *
         * @since 4.4.0
         *
         * @param WP_User $user User object.
         */
        do_action( 'auth_cookie_malformed', $user );

        return false;
    }

    return $user->ID;
}

这个函数稍微有点复杂,咱们拆解一下:

  1. 获取 Cookie 值: 如果没有传入 Cookie 值,就尝试从 $_COOKIE 数组中获取。
  2. 分割 Cookie 值: Cookie 值通常由四个部分组成,用 | 分隔:username|expiration|token|hmac
  3. 数据清洗(DEBUG 模式): 如果开启了 WP_DEBUG,会对 usernameexpiration 进行简单的清理,确保只包含特定字符。
  4. 过滤 (Hook): 使用 auth_cookie_validation 钩子,允许插件或主题修改 Cookie 中的数据。
  5. 基本校验: 检查 usernameexpiration 是否为空,以及 expiration 是否已过期。
  6. 获取用户: 根据 username 从数据库中获取用户信息。
  7. 生成密钥: 使用用户名、密码片段、过期时间和 Token,生成一个密钥。
  8. 生成 HMAC: 使用密钥和原始数据,生成一个 HMAC (Hash-based Message Authentication Code)。HMAC 用于验证数据的完整性,防止被篡改。
  9. 比较 HMAC: 将 Cookie 中的 HMAC 和生成的 HMAC 进行比较。如果不一样,说明 Cookie 被篡改了,验证失败。
  10. 返回用户 ID: 如果所有验证都通过了,就返回用户的 ID。

Cookie 的构成:

组成部分 含义 备注
username 用户名 用于查找用户
expiration 过期时间戳 用于判断 Cookie 是否过期
token 安全令牌 用于增加安全性,防止 Cookie 被盗用后长期有效
hmac HMAC (Hash-based Message Authentication Code),一种消息认证码,通过 Hash 函数和密钥对消息进行加密生成,用于验证数据的完整性。 用于验证 Cookie 的完整性,防止被篡改。 如果 HMAC 不匹配,说明 Cookie 的内容可能被修改过。

Cookie 的种类:

WordPress 使用三种主要的 Cookie 来进行身份验证:

  • LOGGED_IN_COOKIE:用于记住已登录的用户。
  • SECURE_AUTH_COOKIE:用于 HTTPS 连接,提供更安全的身份验证。
  • AUTH_COOKIE:用于非 HTTPS 连接,安全性较低。

四、会话(Session)的秘密:is_user_logged_in()wp_get_current_user()

如果 Cookie 验证失败,determine_current_user() 函数会尝试从会话中获取用户信息。这里涉及到两个函数:

  • is_user_logged_in():检查用户是否已经登录(即,会话中是否存在用户信息)。
  • wp_get_current_user():从会话中获取当前用户信息。
function is_user_logged_in() {
    $user = wp_get_current_user();

    return ( $user->exists() );
}

function wp_get_current_user() {
    global $current_user;

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

        if ( isset( $_SESSION ) && isset( $_SESSION['wp_user_id'] ) && is_numeric( $_SESSION['wp_user_id'] ) ) {
            $user = get_userdata( $_SESSION['wp_user_id'] );
            if ( $user ) {
                $current_user = $user;
            }
        }
    }
    return $current_user;
}

is_user_logged_in() 函数很简单,它只是调用 wp_get_current_user() 函数,然后检查返回的用户对象是否存在。

wp_get_current_user() 函数稍微复杂一点:

  1. 检查 $current_user 首先检查全局变量 $current_user 是否已经是一个 WP_User 对象。如果是,直接返回。
  2. 创建 $current_user 如果 $current_user 不是 WP_User 对象,就创建一个新的 WP_User 对象。
  3. 从会话中获取用户 ID: 检查 $_SESSION 数组中是否存在 wp_user_id 键。如果存在,就从数据库中获取用户信息,并赋值给 $current_user
  4. 返回 $current_user 返回 $current_user 对象。

会话的流程:

  1. 用户登录: 当用户成功登录时,WordPress 会将用户的 ID 存储到 $_SESSION['wp_user_id'] 中。
  2. 后续请求: 在后续的请求中,wp_get_current_user() 函数会从 $_SESSION 中获取用户 ID,并加载用户信息。
  3. 用户注销: 当用户注销时,WordPress 会销毁会话,或者至少删除 $_SESSION['wp_user_id'] 键。

五、钩子(Hooks)的妙用

determine_current_user() 函数在执行前后,分别调用了 do_action() 函数,触发了两个钩子:

  • determine_current_user:在函数开始执行前触发。
  • after_determine_current_user:在函数执行结束后触发。

这两个钩子允许插件或主题在用户身份验证过程中,插入自定义的逻辑。比如:

  • 可以添加自定义的 Cookie 验证机制。
  • 可以根据用户的 IP 地址或浏览器信息,进行额外的身份验证。
  • 可以在用户登录后,执行一些自定义的操作。

六、总结:用户身份验证的流程

让我们总结一下 determine_current_user() 函数的工作流程:

  1. 检查是否已确定用户: 如果 $current_user->ID 不为空,直接返回。
  2. Cookie 验证:
    • 尝试从 LOGGED_IN_COOKIESECURE_AUTH_COOKIEAUTH_COOKIE 中获取 Cookie 值。
    • 调用 wp_validate_auth_cookie() 函数验证 Cookie。
    • 如果 Cookie 验证成功,设置当前用户。
  3. 会话验证:
    • 如果 Cookie 验证失败,检查用户是否已经登录(is_user_logged_in())。
    • 如果用户已经登录,从会话中获取用户信息(wp_get_current_user())。
  4. 钩子: 在函数执行前后,触发钩子,允许插件或主题扩展功能。

七、实战:模拟用户登录流程

为了更好地理解 determine_current_user() 函数的工作原理,我们可以模拟一个简单的用户登录流程:

  1. 用户提交登录信息: 用户在登录表单中输入用户名和密码。
  2. 验证用户: 服务器验证用户名和密码是否正确。
  3. 设置 Cookie: 如果验证成功,服务器生成一个包含用户信息的 Cookie,并发送给客户端。
  4. 后续请求: 在后续的请求中,客户端会将 Cookie 发送给服务器。
  5. determine_current_user() 函数: 服务器端的 determine_current_user() 函数会解析 Cookie,验证用户身份,并设置当前用户。

八、安全注意事项

用户身份验证是个非常重要的安全环节,需要注意以下几点:

  • 使用 HTTPS: 确保网站使用 HTTPS 协议,防止 Cookie 被窃听。
  • 保护 Cookie: 设置 Cookie 的 HttpOnlySecure 标志,防止 Cookie 被 JavaScript 访问和在非 HTTPS 连接中传输。
  • 定期更换密钥: 定期更换用于生成 HMAC 的密钥,防止 Cookie 被破解。
  • 防止暴力破解: 限制登录尝试次数,防止暴力破解密码。
  • 使用强密码: 强制用户使用强密码,增加密码破解的难度。
  • 及时更新 WordPress: 及时更新 WordPress 和插件,修复安全漏洞。

九、总结的总结

determine_current_user() 函数是 WordPress 用户身份验证的核心,它通过 Cookie 和会话信息,识别出当前用户。 理解了这个函数的工作原理,可以更好地理解 WordPress 的用户身份验证机制,并为开发更安全、更可靠的 WordPress 插件和主题打下基础。

好了,今天的讲座就到这里。 希望大家有所收获! 如果有什么问题,欢迎提问。下次再见!

发表回复

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