各位观众老爷,欢迎来到今天的WordPress源码剖析小课堂!今天咱们聊聊WordPress的登录认证流程,重点是wp_signon()
这个关键函数,保证让大家听得懂,看得明白,还能举一反三。
一、登录流程概览:从表单到wp_signon()
WordPress的登录流程,简单来说,就是用户填表单,提交信息,WordPress验证信息,验证通过就给用户发通行证(Cookie),以后用户再来就认通行证了。
咱们先从用户提交登录表单开始,一步一步追踪到wp_signon()
这个核心函数。
-
用户提交登录表单: 用户在
wp-login.php
页面填写用户名和密码,点击“登录”按钮。 -
表单数据处理:
wp-login.php
会接收到POST请求,包含log
(用户名)和pwd
(密码)等字段。 -
wp-login.php
核心逻辑:wp-login.php
会调用wp_signon()
函数来处理登录认证。
二、wp_signon()
源码剖析:通行证发放机关
wp_signon()
函数位于wp-includes/pluggable.php
文件中。这个函数是WordPress登录认证的核心,负责验证用户信息,并设置认证Cookie。
咱们来仔细看看wp_signon()
的源码实现。为了便于理解,我将源码拆解成几个关键部分,并加上详细的注释。
/**
* Signs on a user.
*
* Tries to sign in a user with the specified credentials.
*
* @since 2.5.0
*
* @param array|object $credentials User login credentials. Expected to be an array
* or object containing a 'user_login' and 'user_password' index.
* @param bool $secure Whether the admin cookies should be secure.
* Defaults to false.
*
* @return WP_User|WP_Error WP_User on success, WP_Error on failure.
*/
function wp_signon( $credentials = array(), $secure = '' ) {
// 1. 确保传入的是数组或者对象
if ( empty( $credentials ) ) {
return new WP_Error( 'empty_credentials', __( 'Empty credentials.' ) );
}
// 2. 如果传入的是对象,转换成数组
if ( is_object( $credentials ) ) {
$credentials = (array) $credentials;
}
// 3. 预处理用户名和密码,去除空格
$username = trim( $credentials['user_login'] );
$password = $credentials['user_password'];
// 4. 创建一个 WP_User 对象
$user = new WP_User();
// 5. 登录验证前的钩子,允许插件进行自定义验证
/**
* Fires before the user is authenticated.
*
* @since 2.8.0
*
* @param string $username The username.
* @param string $password The password.
*/
do_action( 'wp_authenticate', $username, $password );
// 6. 使用 authenticate 过滤器,允许其他函数介入验证过程
$user = apply_filters( 'authenticate', $user, $username, $password );
// 7. 如果 authenticate 过滤器返回 WP_Error 对象,说明验证失败
if ( is_wp_error( $user ) ) {
return $user;
}
// 8. 如果 authenticate 过滤器返回 null,说明验证失败,尝试使用默认的验证方式
if ( null === $user ) {
$user = wp_authenticate( $username, $password );
if ( is_wp_error( $user ) ) {
return $user;
}
}
// 9. 如果用户不存在,返回错误
if ( ! $user->exists() ) {
return new WP_Error( 'invalid_username', __( 'Invalid username or email address.' ) );
}
// 10. 检查用户是否被禁用
if ( ! empty( $user->errors ) ) {
return new WP_Error( 'user_disabled', __( 'Your account is disabled.' ) );
}
// 11. 安全性考虑:生成密钥时,判断是否需要使用安全Cookie
if ( empty( $secure ) ) {
$secure = is_ssl();
}
/**
* Filters whether the admin cookies should be secure.
*
* @since 3.1.0
*
* @param bool $secure Whether the admin cookies should be secure.
*/
$secure = apply_filters( 'secure_signon_cookie', $secure, $credentials );
// 12. 登录成功后的操作,设置认证Cookie
wp_set_auth_cookie( $user->ID, false, $secure );
/**
* Fires after the user is signed on.
*
* @since 2.5.0
*
* @param string $username The username.
* @param WP_User $user The WP_User object for the sign-on user.
*/
do_action( 'wp_login', $username, $user );
// 13. 返回 WP_User 对象
return $user;
}
wp_signon()
流程分解:
步骤 | 描述 | 涉及的关键函数/变量 | 作用 |
---|---|---|---|
1 | 检查传入的参数是否为空,如果为空,返回错误。 | empty() |
保证函数接收到有效的用户信息。 |
2 | 如果传入的参数是对象,将其转换为数组。 | is_object() , (array) |
统一数据类型,方便后续处理。 |
3 | 去除用户名和密码两端的空格。 | trim() |
防止用户输入错误导致验证失败。 |
4 | 创建一个WP_User 对象,用于存储用户信息。 |
new WP_User() |
创建一个用于存储用户信息的对象。 |
5 | 触发wp_authenticate 动作钩子,允许插件在验证之前执行自定义操作。 |
do_action( 'wp_authenticate', $username, $password ) |
提供一个扩展点,允许插件修改验证逻辑,例如添加验证码验证等。 |
6 | 应用authenticate 过滤器,允许其他函数介入验证过程。 |
apply_filters( 'authenticate', $user, $username, $password ) |
提供一个更强大的扩展点,允许完全自定义验证流程,例如使用OAuth登录等。 |
7 | 如果authenticate 过滤器返回WP_Error 对象,说明验证失败,直接返回错误。 |
is_wp_error() |
提前结束流程,返回错误信息。 |
8 | 如果authenticate 过滤器返回null ,说明验证未完成,尝试使用默认的验证方式wp_authenticate 。 |
wp_authenticate( $username, $password ) |
使用WordPress默认的用户名/密码验证方式进行验证。 |
9 | 如果用户不存在,返回错误。 | $user->exists() |
确保用户存在于数据库中。 |
10 | 检查用户是否被禁用,如果被禁用,返回错误。 | ! empty( $user->errors ) |
防止被禁用的用户登录。 |
11 | 确定是否使用安全Cookie。 | is_ssl() , apply_filters( 'secure_signon_cookie', $secure, $credentials ) |
保证在HTTPS环境下使用安全Cookie,提高安全性。 |
12 | 设置认证Cookie,允许用户在后续访问中保持登录状态。 | wp_set_auth_cookie( $user->ID, false, $secure ) |
设置Cookie,让浏览器记住用户的登录状态。 |
13 | 触发wp_login 动作钩子,允许插件在登录成功后执行自定义操作。 |
do_action( 'wp_login', $username, $user ) |
提供一个扩展点,允许插件在用户登录后执行自定义操作,例如记录登录日志等。 |
14 | 返回WP_User 对象,表示登录成功。 |
$user |
返回用户信息,供后续使用。 |
三、wp_authenticate()
:默认验证逻辑
wp_authenticate()
函数负责默认的用户名/密码验证逻辑。它也是一个可插拔函数,允许开发者自定义验证方式。
/**
* Authenticates a user, confirming the login name and password are valid.
*
* @since 3.2.0
*
* @param string $username Username or email address.
* @param string $password User password.
*
* @return WP_User|WP_Error WP_User on success, WP_Error on failure.
*/
function wp_authenticate( $username, $password ) {
$username = sanitize_user( $username );
if ( empty( $username ) ) {
return new WP_Error( 'required', __( 'Username is required.' ) );
}
if ( empty( $password ) ) {
return new WP_Error( 'required', __( 'Password is required.' ) );
}
/** @var WP_User|WP_Error $user */
$user = apply_filters( 'wp_authenticate_user', null, $username, $password );
if ( $user ) {
return $user;
}
$user = get_user_by( 'login', $username );
if ( ! $user ) {
$user = get_user_by( 'email', $username );
}
if ( ! $user ) {
return new WP_Error( 'invalid_username', __( 'Invalid username or email address.' ) );
}
/**
* Fires before the user is authenticated, after the user object is retrieved.
*
* @since 3.2.0
*
* @param WP_User $user WP_User object for the matching username.
* @param string $password The password to test against the user.
*/
do_action( 'wp_authenticate_user', $user, $password );
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
/**
* Fires after an invalid authentication attempt.
*
* @since 3.8.0
*
* @param string $username Username or email address.
* @param WP_User $user WP_User object for the matching username.
*/
do_action( 'wp_login_failed', $username, $user );
return new WP_Error( 'incorrect_password', sprintf( __( 'The password you entered for the username %1$s is incorrect.' ), '<strong>' . $username . '</strong>' ) );
}
if ( is_wp_error( $user ) ) {
return $user;
}
$user = apply_filters( 'wp_authenticate_cookie', $user, $username, $password );
return $user;
}
wp_authenticate()
流程分解:
- 数据清洗: 对用户名进行
sanitize_user()
处理,防止恶意注入。 - 非空验证: 检查用户名和密码是否为空,如果为空,返回错误。
wp_authenticate_user
过滤器: 允许插件自定义验证逻辑。- 获取用户: 尝试通过用户名或邮箱获取
WP_User
对象。 - 用户存在性验证: 如果用户不存在,返回错误。
wp_authenticate_user
动作钩子: 允许插件在验证密码之前执行自定义操作。- 密码验证: 使用
wp_check_password()
函数验证密码是否正确。 wp_login_failed
动作钩子: 如果密码验证失败,触发wp_login_failed
动作钩子。wp_authenticate_cookie
过滤器: 允许插件修改用于设置Cookie的用户对象。- 返回
WP_User
对象: 如果验证成功,返回WP_User
对象。
四、wp_check_password()
:密码验证
wp_check_password()
函数位于wp-includes/pluggable.php
文件中,负责验证用户输入的密码是否与数据库中存储的哈希密码匹配。这个函数支持多种哈希算法,包括传统的MD5和现代的bcrypt。
/**
* Checks whether the given password is correct.
*
* @since 2.5.0
*
* @param string $password Plaintext user password to test.
* @param string $hash The encrypted password hash.
* @param string $user_id Optional. The user ID.
* @return bool False, if the password doesn't match the hash. True, if it does.
*/
function wp_check_password( $password, $hash, $user_id = '' ) {
global $wp_hasher;
// If the hash is still MD5, re-hash it, and store with the newer PHPass.
if ( substr( $hash, 0, 2 ) == '$P' ) {
return hash_equals( $hash, crypt( $password, $hash ) );
}
// If the stored hash is longer than 80 characters, assume it's bcrypt.
if ( strlen( $hash ) > 80 ) {
if ( ! class_exists( 'PasswordHash' ) ) {
require_once ABSPATH . WPINC . '/class-phpass.php';
}
$wp_hasher = new PasswordHash( 8, true );
return $wp_hasher->CheckPassword( $password, $hash );
}
if ( empty( $wp_hasher ) ) {
require_once ABSPATH . WPINC . '/class-phpass.php';
$wp_hasher = new PasswordHash( 8, true );
}
// Check the hash.
$check = $wp_hasher->CheckPassword( $password, $hash );
/**
* Fires after a password has been checked for validity.
*
* @since 4.2.0
*
* @param bool $check Whether the password is valid.
* @param string $password The password in plain text.
* @param string $hash The password hash to check against.
* @param string $user_id The user ID.
*/
do_action( 'check_password', $check, $password, $hash, $user_id );
return $check;
}
wp_check_password()
流程分解:
- MD5兼容: 如果哈希值以
$P
开头,说明是传统的MD5哈希,使用crypt()
函数进行验证。 - Bcrypt支持: 如果哈希值长度大于80个字符,说明是bcrypt哈希,使用
PasswordHash
类进行验证。 - PHPass支持: 对于其他哈希类型,使用
PasswordHash
类进行验证。 check_password
动作钩子: 允许插件在密码验证后执行自定义操作。- 返回验证结果: 返回
true
表示密码正确,false
表示密码错误。
五、wp_set_auth_cookie()
:发放通行证
wp_set_auth_cookie()
函数位于wp-includes/pluggable.php
文件中,负责设置认证Cookie,让浏览器记住用户的登录状态。
/**
* Sets the authentication cookies.
*
* @since 2.5.0
*
* @param int $user_id User ID.
* @param bool $remember Whether to remember the user. Default is false.
* @param bool $secure Whether the admin cookies should be secure.
*/
function wp_set_auth_cookie( $user_id, $remember = false, $secure = '' ) {
if ( empty( $secure ) ) {
$secure = is_ssl();
}
$expiration = time() + DAY_IN_SECONDS;
$rememberme = false;
if ( $remember ) {
$expiration = time() + ( ( $rememberme = true ) ? WEEK_IN_SECONDS : DAY_IN_SECONDS );
}
if ( $secure ) {
$secure_logged_in_cookie = true;
setcookie( LOGGED_IN_COOKIE, wp_generate_auth_cookie( $user_id, 'logged_in', $secure_logged_in_cookie ), $expiration, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, $secure, true );
setcookie( SECURE_AUTH_COOKIE, wp_generate_auth_cookie( $user_id, 'secure_auth', $secure ), $expiration, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, true, true );
/**
* Fires after the secure authentication cookies are set.
*
* @since 3.7.0
*
* @param int $user_id User ID.
* @param bool $remember Whether to remember the user. Default is false.
* @param bool $secure Whether the admin cookies should be secure.
* @param int $expiration Time the cookie expires in seconds.
*/
do_action( 'set_auth_cookie', $user_id, $remember, $secure, $expiration );
} else {
$secure_logged_in_cookie = is_ssl();
setcookie( LOGGED_IN_COOKIE, wp_generate_auth_cookie( $user_id, 'logged_in', $secure_logged_in_cookie ), $expiration, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true );
setcookie( AUTH_COOKIE, wp_generate_auth_cookie( $user_id, 'auth', $secure_logged_in_cookie ), $expiration, SITECOOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true );
setcookie( SECURE_AUTH_COOKIE, wp_generate_auth_cookie( $user_id, 'secure_auth', $secure_logged_in_cookie ), $expiration, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, true, true );
/**
* Fires after the authentication cookies are set.
*
* @since 2.5.0
*
* @param int $user_id User ID.
* @param bool $remember Whether to remember the user. Default is false.
* @param bool $secure Whether the admin cookies should be secure.
* @param int $expiration Time the cookie expires in seconds.
*/
do_action( 'set_auth_cookie', $user_id, $remember, $secure, $expiration );
}
if ( $rememberme ) {
setcookie( 'wp-saving-post', $_SERVER['REQUEST_URI'], time() + YEAR_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure, true );
}
}
wp_set_auth_cookie()
流程分解:
- 确定是否使用安全Cookie: 根据当前连接是否为HTTPS确定是否使用安全Cookie。
- 设置Cookie过期时间: 如果用户选择了“记住我”,则Cookie过期时间设置为一周,否则设置为一天。
- 生成认证Cookie值: 使用
wp_generate_auth_cookie()
函数生成认证Cookie的值。 - 设置Cookie: 使用
setcookie()
函数设置认证Cookie,包括LOGGED_IN_COOKIE
、AUTH_COOKIE
和SECURE_AUTH_COOKIE
。 set_auth_cookie
动作钩子: 允许插件在设置认证Cookie后执行自定义操作。
六、总结
今天咱们深入剖析了WordPress的登录认证流程,重点讲解了wp_signon()
函数及其依赖的wp_authenticate()
、wp_check_password()
和wp_set_auth_cookie()
等关键函数。通过这次源码之旅,相信大家对WordPress的登录机制有了更深入的理解。
记住,理解源码不是目的,目的是能够利用源码解决实际问题,甚至修改源码来实现更强大的功能。希望今天的课程能帮助大家在WordPress开发的道路上更进一步!
下课!