阐述 WordPress 的登录认证流程,从提交表单到 `wp_signon()` 的具体源码实现。

各位观众老爷,欢迎来到今天的WordPress源码剖析小课堂!今天咱们聊聊WordPress的登录认证流程,重点是wp_signon()这个关键函数,保证让大家听得懂,看得明白,还能举一反三。

一、登录流程概览:从表单到wp_signon()

WordPress的登录流程,简单来说,就是用户填表单,提交信息,WordPress验证信息,验证通过就给用户发通行证(Cookie),以后用户再来就认通行证了。

咱们先从用户提交登录表单开始,一步一步追踪到wp_signon()这个核心函数。

  1. 用户提交登录表单: 用户在wp-login.php页面填写用户名和密码,点击“登录”按钮。

  2. 表单数据处理: wp-login.php会接收到POST请求,包含log(用户名)和pwd(密码)等字段。

  3. 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()流程分解:

  1. 数据清洗: 对用户名进行sanitize_user()处理,防止恶意注入。
  2. 非空验证: 检查用户名和密码是否为空,如果为空,返回错误。
  3. wp_authenticate_user过滤器: 允许插件自定义验证逻辑。
  4. 获取用户: 尝试通过用户名或邮箱获取WP_User对象。
  5. 用户存在性验证: 如果用户不存在,返回错误。
  6. wp_authenticate_user动作钩子: 允许插件在验证密码之前执行自定义操作。
  7. 密码验证: 使用wp_check_password()函数验证密码是否正确。
  8. wp_login_failed动作钩子: 如果密码验证失败,触发wp_login_failed动作钩子。
  9. wp_authenticate_cookie过滤器: 允许插件修改用于设置Cookie的用户对象。
  10. 返回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()流程分解:

  1. MD5兼容: 如果哈希值以$P开头,说明是传统的MD5哈希,使用crypt()函数进行验证。
  2. Bcrypt支持: 如果哈希值长度大于80个字符,说明是bcrypt哈希,使用PasswordHash类进行验证。
  3. PHPass支持: 对于其他哈希类型,使用PasswordHash类进行验证。
  4. check_password动作钩子: 允许插件在密码验证后执行自定义操作。
  5. 返回验证结果: 返回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()流程分解:

  1. 确定是否使用安全Cookie: 根据当前连接是否为HTTPS确定是否使用安全Cookie。
  2. 设置Cookie过期时间: 如果用户选择了“记住我”,则Cookie过期时间设置为一周,否则设置为一天。
  3. 生成认证Cookie值: 使用wp_generate_auth_cookie()函数生成认证Cookie的值。
  4. 设置Cookie: 使用setcookie()函数设置认证Cookie,包括LOGGED_IN_COOKIEAUTH_COOKIESECURE_AUTH_COOKIE
  5. set_auth_cookie动作钩子: 允许插件在设置认证Cookie后执行自定义操作。

六、总结

今天咱们深入剖析了WordPress的登录认证流程,重点讲解了wp_signon()函数及其依赖的wp_authenticate()wp_check_password()wp_set_auth_cookie()等关键函数。通过这次源码之旅,相信大家对WordPress的登录机制有了更深入的理解。

记住,理解源码不是目的,目的是能够利用源码解决实际问题,甚至修改源码来实现更强大的功能。希望今天的课程能帮助大家在WordPress开发的道路上更进一步!

下课!

发表回复

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