嘿,各位代码爱好者,欢迎来到今天的 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 );
}
是不是感觉有点简单? 别着急,魔鬼藏在细节里。 它主要做了两件事:
- 声明全局变量
$current_user
:global $current_user;
这行代码告诉 PHP,我们要使用全局范围内的$current_user
变量。这个变量存储了当前登录用户的相关信息。 - 判断
$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;
}
这个函数做了以下几件事:
- 初始化
$current_user
: 如果$current_user
变量还不是WP_User
类的实例,那么就创建一个新的WP_User
对象。 - 执行
set_current_user
动作:do_action( 'set_current_user' );
这行代码允许其他插件或主题在用户对象设置之前执行一些操作,比如自定义身份验证。 - 调用
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 );
}
}
这里主要做的事情:
- 类型转换和判断: 首先,将传入的
$id
参数转换为整数类型。 如果$id
为 0,则创建一个游客用户(WP_User
对象,ID 为 0)。 - 通过
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;
}
简单梳理一下:
- 获取 Cookie: 函数首先尝试从
$_COOKIE
数组中获取登录 Cookie。WordPress 使用LOGGED_IN_COOKIE
和SECURE_AUTH_COOKIE
这两个常量来定义 Cookie 的名称。SECURE_AUTH_COOKIE
用于 HTTPS 连接,而LOGGED_IN_COOKIE
用于普通的 HTTP 连接。 - 解析 Cookie: 如果找到了 Cookie,函数会将 Cookie 的值按照
|
分隔符拆分成多个部分,包括用户名、过期时间、token 和 HMAC(哈希消息认证码)。 - 验证 HMAC: HMAC 用于验证 Cookie 的完整性,防止 Cookie 被篡改。 函数会使用
wp_hash()
函数生成一个密钥,然后使用这个密钥和 Cookie 中的其他信息计算出一个 HMAC 值,并与 Cookie 中的 HMAC 值进行比较。 如果两个 HMAC 值不相等,那么就认为 Cookie 被篡改,验证失败。 - 验证过期时间: 函数会检查 Cookie 的过期时间,如果已经过期,那么就认为 Cookie 无效,验证失败。
- 根据用户名获取用户: 函数会使用
get_user_by( 'login', $username )
函数根据用户名从数据库中获取用户的信息。 - 验证 Token: 从数据库中获取用户的会话 token,并与Cookie中的token进行比对。
- 设置当前用户: 如果 Cookie 验证通过,函数会调用
wp_set_current_user( $user->ID )
函数将当前用户设置为登录状态。 - 触发
auth_cookie_validated
动作:do_action( 'auth_cookie_validated', $user->ID, $scheme );
这行代码允许其他插件或主题在 Cookie 验证通过后执行一些操作。 - 返回用户 ID: 函数返回用户的 ID,表示 Cookie 验证成功。
8. 总结:is_user_logged_in()
的工作流程
现在,我们把整个流程串起来,总结一下 is_user_logged_in()
函数的工作原理:
- 调用
is_user_logged_in()
: 当 WordPress 需要判断用户是否登录时,会调用is_user_logged_in()
函数。 - 检查
$current_user->ID
:is_user_logged_in()
函数会检查全局变量$current_user
的ID
属性是否为空。 如果不为空,就认为用户已经登录,返回true
。 - 初始化
$current_user
(如果需要): 如果$current_user
变量还没有被初始化,或者$current_user->ID
为空,那么 WordPress 会调用wp_get_current_user()
函数来尝试获取用户信息。 - 读取 Cookie:
wp_get_current_user()
函数会调用wp_set_current_user()
函数,wp_set_current_user()
函数会尝试从 Cookie 中读取用户 ID 和认证信息。 - 验证 Cookie:
wp_set_current_user()
函数会调用wp_validate_auth_cookie()
函数来验证 Cookie 的有效性。wp_validate_auth_cookie()
函数会检查 Cookie 的完整性、过期时间,并与数据库中的数据进行比对。 - 设置
$current_user
: 如果 Cookie 验证通过,wp_set_current_user()
函数会根据 Cookie 中的用户 ID 从数据库中获取用户的详细信息,并将这些信息存储到$current_user
变量中。 - 返回登录状态:
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_COOKIE 或 SECURE_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 专家! 下次再见!