深入理解 `wp_signon()` 函数的源码,它是如何验证用户名和密码,并设置登录 `Cookie` 的?

各位观众老爷们,大家好!今天咱们来聊聊 WordPress 登录背后的男人—— wp_signon() 函数。别看它名字平平无奇,但它可是 WordPress 登录的灵魂人物,负责验证你的身份,然后给你发个“通行证”(Cookie),让你在 WordPress 的世界里畅行无阻。

咱们今天就来扒一扒它的源码,看看它是怎么做到这一切的。请各位坐稳扶好,咱们发车啦!

一、 wp_signon() 的身世背景

wp_signon() 函数位于 wp-includes/pluggable.php 文件中,它的主要作用就是:

  1. 验证用户名和密码: 看看你提供的用户名和密码是否正确,是否与数据库中的记录匹配。
  2. 设置登录 Cookie: 如果验证通过,就设置登录 Cookie,让 WordPress 记住你的身份。
  3. 返回用户信息: 如果验证成功,返回用户信息对象,方便你在其他地方使用。

二、源码剖析:一行一行地扒它的皮

咱们先来看看 wp_signon() 函数的庐山真面目(简化版,去掉了部分不常用的参数和逻辑):

function wp_signon( $credentials = array(), $secure_cookie = '' ) {
    /**
     * Fires before the user is signed on.
     *
     * @since 2.5.0
     *
     * @param array $credentials An array of user signon credentials.
     * @param bool  $secure_cookie Whether the session is using a secure cookie.
     */
    do_action( 'wp_signon', $credentials, $secure_cookie );

    $secure_cookie = apply_filters( 'secure_signon_cookie', $secure_cookie, $credentials );

    $user = wp_authenticate( $credentials['user_login'], $credentials['user_password'] );

    if ( is_wp_error( $user ) ) {
        return $user;
    }

    wp_set_auth_cookie( $user->ID, false, $secure_cookie );
    do_action( 'wp_login', $user->user_login, $user );

    return $user;
}

别害怕,代码不多,咱们一步一步来。

  1. do_action( 'wp_signon', $credentials, $secure_cookie );

    这是个“钩子”(Hook),允许其他插件或主题在用户登录之前执行一些操作。你可以理解为,wp_signon() 函数在登录之前,先问问大家:“还有谁要插一脚吗?”。

  2. $secure_cookie = apply_filters( 'secure_signon_cookie', $secure_cookie, $credentials );

    这又是个“钩子”,允许修改 secure_cookie 的值。secure_cookie 决定了登录 Cookie 是否只能通过 HTTPS 传输。一般来说,如果你的网站启用了 HTTPS,就应该设置为 true,以保证安全。

  3. $user = wp_authenticate( $credentials['user_login'], $credentials['user_password'] );

    这才是重头戏!wp_authenticate() 函数负责验证用户名和密码。它会检查数据库,看看你提供的用户名和密码是否匹配。如果匹配,就返回用户信息对象;如果不匹配,就返回一个 WP_Error 对象,告诉你登录失败的原因。

    咱们再深入 wp_authenticate() 函数看看:

    function wp_authenticate( $username, $password ) {
        $username = sanitize_user( $username );
        $password = trim( $password );
    
        /**
         * Fires before a user is authenticated.
         *
         * @since 2.5.0
         *
         * @param string $username Username passed to wp_authenticate.
         * @param string $password Password passed to wp_authenticate.
         */
        do_action( 'wp_authenticate', $username, $password );
    
        $errors = new WP_Error();
    
        if ( empty( $username ) ) {
            $errors->add( 'empty_username', __( '<strong>错误:</strong> 请输入用户名。' ) );
        } elseif ( ! validate_username( $username ) ) {
            $errors->add( 'invalid_username', __( '<strong>错误:</strong> 无效的用户名。' ) );
            $username = '';
        }
    
        if ( empty( $password ) ) {
            $errors->add( 'empty_password', __( '<strong>错误:</strong> 请输入密码。' ) );
        }
    
        if ( $errors->has_errors() ) {
            return $errors;
        }
    
        $user = get_user_by( 'login', $username );
    
        if ( ! $user ) {
            /**
             * Fires after a user fails authentication.
             *
             * @since 2.1.0
             *
             * @param string $username Username passed to wp_authenticate.
             * @param string $password Password passed to wp_authenticate.
             * @param WP_Error $errors   A WP_Error object.
             */
            do_action( 'wp_login_failed', $username, $password, $errors );
    
            $errors->add( 'invalid_username', __( '<strong>错误:</strong> 用户名错误。' ) );
    
            return $errors;
        }
    
        $user = apply_filters( 'wp_authenticate_user', $user, $password );
        if ( is_wp_error( $user ) ) {
            /**
             * Fires after a user fails authentication.
             *
             * @since 2.1.0
             *
             * @param string $username Username passed to wp_authenticate.
             * @param string $password Password passed to wp_authenticate.
             * @param WP_Error $errors   A WP_Error object.
             */
            do_action( 'wp_login_failed', $username, $password, $user );
    
            return $user;
        }
    
        if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
            /**
             * Fires after a user fails authentication.
             *
             * @since 2.1.0
             *
             * @param string $username Username passed to wp_authenticate.
             * @param string $password Password passed to wp_authenticate.
             * @param WP_Error $errors   A WP_Error object.
             */
            do_action( 'wp_login_failed', $username, $password, $errors );
    
            $errors->add( 'incorrect_password', __( '<strong>错误:</strong> 密码错误。' ) );
    
            return $errors;
        }
    
        wp_cache_delete( $user->ID, 'user_meta' );
    
        return $user;
    }

    wp_authenticate() 函数的逻辑如下:

    • 数据清理: 使用 sanitize_user() 清理用户名,使用 trim() 去除密码两端的空格。
    • 空值检查: 检查用户名和密码是否为空。如果为空,就返回错误。
    • 用户名验证: 使用 validate_username() 验证用户名是否有效。
    • 获取用户信息: 使用 get_user_by( 'login', $username ) 根据用户名从数据库中获取用户信息。
    • 用户是否存在: 如果用户不存在,就返回错误。
    • 密码验证: 使用 wp_check_password() 验证密码是否正确。这是个关键函数,咱们待会儿重点讲。
    • 清除缓存: 使用 wp_cache_delete() 清除用户元数据的缓存。
    • 返回用户信息: 如果一切顺利,就返回用户信息对象。

    咱们重点关注一下 wp_check_password() 函数:

    function wp_check_password( $password, $hash, $user_id = '' ) {
        global $wp_hasher;
    
        if ( empty( $wp_hasher ) ) {
            require_once ABSPATH . WPINC . '/class-phpass.php';
            $wp_hasher = new PasswordHash( 8, true );
        }
    
        $check = $wp_hasher->CheckPassword( $password, $hash );
    
        /**
         * Filters whether the given password is valid.
         *
         * @since 2.5.0
         *
         * @param bool   $check   Whether the password is valid.
         * @param string $password The password to check.
         * @param string $hash     The stored password hash.
         * @param int    $user_id  User ID.
         */
        return apply_filters( 'check_password', $check, $password, $hash, $user_id );
    }

    wp_check_password() 函数使用了 PasswordHash 类来验证密码。PasswordHash 类使用了 bcrypt 算法来对密码进行哈希处理,保证密码的安全性。

    简单来说,wp_check_password() 函数会把用户输入的密码也用 bcrypt 算法进行哈希处理,然后和数据库中存储的哈希值进行比较。如果相同,就说明密码正确;否则,密码错误。

  4. if ( is_wp_error( $user ) ) { return $user; }

    如果 wp_authenticate() 函数返回了一个 WP_Error 对象,就说明登录失败了,wp_signon() 函数会直接返回这个错误对象。

  5. wp_set_auth_cookie( $user->ID, false, $secure_cookie );

    如果 wp_authenticate() 函数返回了用户信息对象,就说明登录成功了,wp_signon() 函数会调用 wp_set_auth_cookie() 函数来设置登录 Cookie。

    咱们再深入 wp_set_auth_cookie() 函数看看:

    function wp_set_auth_cookie( $user_id, $remember = false, $secure = '' ) {
        if ( empty( $secure ) ) {
            $secure = is_ssl();
        }
    
        $expiration = time() + apply_filters( 'auth_cookie_expiration', ( $remember ? 3024000 : 1209600 ), $user_id, $remember );
        $cookie     = wp_generate_auth_cookie( $user_id, $expiration, 'auth', $secure );
    
        do_action( 'set_auth_cookie', $cookie, $user_id, $expiration, 'auth', $remember, $secure );
    
        setcookie( AUTH_COOKIE, $cookie, $expiration, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, $secure, true );
    
        if ( COOKIEPATH != SITECOOKIEPATH ) {
            setcookie( AUTH_COOKIE, $cookie, $expiration, SITECOOKIEPATH, COOKIE_DOMAIN, $secure, true );
        }
    
        if ( $remember ) {
            wp_set_remember_cookie( $user_id, $expiration, $secure );
        }
    
        /**
         * Fires after the authentication cookie is set.
         *
         * @since 3.5.0
         *
         * @param string $cookie    The authentication cookie.
         * @param int    $user_id   User ID.
         * @param int    $expiration The time the cookie expires.
         * @param string $scheme    Authentication scheme. Default 'auth'.
         */
        do_action( 'auth_cookie_set', $cookie, $user_id, $expiration, 'auth' );
    }

    wp_set_auth_cookie() 函数的逻辑如下:

    • 确定是否安全: 如果没有指定 secure,就根据当前是否是 HTTPS 来确定。
    • 计算过期时间: 根据是否选择了“记住我”来计算 Cookie 的过期时间。
    • 生成 Cookie: 使用 wp_generate_auth_cookie() 函数生成 Cookie 的值。
    • 设置 Cookie: 使用 setcookie() 函数设置 Cookie。
    • 设置“记住我”Cookie: 如果选择了“记住我”,就调用 wp_set_remember_cookie() 函数来设置“记住我”Cookie。

    咱们再深入 wp_generate_auth_cookie() 函数看看:

    function wp_generate_auth_cookie( $user_id, $expiration, $scheme = 'auth', $token = '' ) {
        $pass_frag = substr( wp_hash( get_user_meta( $user_id, 'session_tokens', true ) . '|' . get_user_meta( $user_id, 'pass', true ) ), 8, 4 );
    
        $key = wp_hash( $user_id . '|' . $pass_frag . '|' . $expiration . '|' . $scheme . '|' . $token );
    
        $hash = hash_hmac( 'md5', $user_id . '|' . $expiration . '|' . $scheme . '|' . $token, $key );
    
        $cookie = $user_id . '|' . $expiration . '|' . $hash . '|' . $scheme . '|' . $token;
    
        /**
         * Filters the authentication cookie.
         *
         * @since 2.6.0
         *
         * @param string $cookie    Authentication cookie.
         * @param int    $user_id   User ID.
         * @param int    $expiration The time the cookie expires.
         * @param string $scheme    Authentication scheme. Default 'auth'.
         */
        return apply_filters( 'auth_cookie', $cookie, $user_id, $expiration, $scheme );
    }

    wp_generate_auth_cookie() 函数会生成一个包含用户 ID、过期时间、哈希值、登录方案和令牌的字符串,作为 Cookie 的值。这个哈希值是通过一系列哈希算法生成的,保证 Cookie 的安全性。

    至此,咱们就完成了登录 Cookie 的设置。

  6. do_action( 'wp_login', $user->user_login, $user );

    这是个“钩子”,允许其他插件或主题在用户登录之后执行一些操作。你可以理解为,wp_signon() 函数在登录之后,再问问大家:“还有谁要庆祝一下吗?”。

  7. return $user;

    wp_signon() 函数最终会返回用户信息对象,方便你在其他地方使用。

三、总结:wp_signon() 的工作流程

咱们用一张表格来总结一下 wp_signon() 函数的工作流程:

步骤 函数/操作 说明
1 do_action( 'wp_signon' ) 登录前钩子,允许插件或主题在登录之前执行操作。
2 apply_filters( 'secure_signon_cookie' ) 允许修改 secure_cookie 的值,决定登录 Cookie 是否只能通过 HTTPS 传输。
3 wp_authenticate() 验证用户名和密码。
4 sanitize_user() 清理用户名。
5 trim() 去除密码两端的空格。
6 空值检查 检查用户名和密码是否为空。
7 validate_username() 验证用户名是否有效。
8 get_user_by( 'login', $username ) 根据用户名从数据库中获取用户信息。
9 用户是否存在 检查用户是否存在。
10 wp_check_password() 验证密码是否正确。使用 bcrypt 算法对密码进行哈希处理,并与数据库中的哈希值进行比较。
11 wp_cache_delete() 清除用户元数据的缓存。
12 wp_set_auth_cookie() 设置登录 Cookie。
13 wp_generate_auth_cookie() 生成 Cookie 的值,包含用户 ID、过期时间、哈希值、登录方案和令牌。
14 setcookie() 设置 Cookie。
15 wp_set_remember_cookie() 如果选择了“记住我”,就设置“记住我”Cookie。
16 do_action( 'wp_login' ) 登录后钩子,允许插件或主题在登录之后执行操作。
17 return $user 返回用户信息对象。

四、安全性考量:wp_signon() 如何保护你的账户

  • 密码哈希: 使用 bcrypt 算法对密码进行哈希处理,保证密码的安全性。即使数据库泄露,攻击者也无法直接获取用户的明文密码。
  • Cookie 安全: 生成 Cookie 时,使用了哈希算法,防止 Cookie 被篡改。
  • HTTPS 支持: 可以通过 secure_cookie 参数来强制使用 HTTPS 传输 Cookie,防止 Cookie 被窃听。
  • 钩子机制: 提供了大量的钩子,允许开发者自定义登录流程,增强安全性。

五、总结与展望

wp_signon() 函数是 WordPress 登录的核心,它负责验证用户身份,并设置登录 Cookie。通过深入了解它的源码,我们可以更好地理解 WordPress 的登录机制,并可以根据自己的需求进行定制和扩展。

当然,WordPress 的登录机制还有很多细节,例如“记住我”功能、密码重置功能等等。咱们今天就先聊到这里,以后有机会再深入探讨。

希望今天的讲座对大家有所帮助!谢谢大家!

发表回复

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