阐述 WordPress 如何通过 `wp_set_auth_cookie()` 函数实现持久化登录。

各位观众老爷们,大家好!我是你们的老朋友,Bug Killer。今天,咱们来聊聊 WordPress 登录背后的秘密武器——wp_set_auth_cookie()函数。

开场白:Cookie 的身世之谜

想象一下,你去一家咖啡馆,点了一杯咖啡。服务员给了你一个号码牌,下次你再来,只要亮出这个号码牌,服务员就知道你是谁,上次点了什么。这里的号码牌,就相当于浏览器中的 Cookie。

Cookie 是服务器存储在用户浏览器中的小型文本文件,用于记住用户的信息。当用户再次访问网站时,浏览器会将 Cookie 发送给服务器,服务器就可以根据 Cookie 识别用户。

在 WordPress 中,当你输入用户名和密码,点击“登录”按钮时,WordPress 会验证你的身份。验证成功后,它会使用 wp_set_auth_cookie() 函数在你的浏览器中设置一个或多个 Cookie,用于记住你的登录状态。下次你再次访问网站时,浏览器会自动发送这些 Cookie,WordPress 就可以自动登录你,而无需再次输入用户名和密码。这就是所谓的“持久化登录”。

wp_set_auth_cookie() 函数:登录的幕后英雄

wp_set_auth_cookie() 函数是 WordPress 实现持久化登录的核心。它的作用是设置用于身份验证的 Cookie。

/**
 * Sets the authentication cookies.
 *
 * @since 2.5.0
 *
 * @param int    $user_id User ID.
 * @param bool   $remember Whether to remember the user. Default false.
 * @param mixed  $secure  Whether the admin cookies should only be sent over HTTPS.
 *                        Default is to base on is_ssl(). Pass true to force.
 * @param string $token   Session token.
 */
function wp_set_auth_cookie( $user_id, $remember = false, $secure = '', $token = '' ) {
    $secure = apply_filters( 'secure_auth_cookie', is_ssl(), $user_id );

    if ( ! empty( $token ) ) {
        wp_set_current_user( $user_id );
        wp_validate_auth_cookie( '', 'logged_in' ); // Regenerate the cookie with the new token.
    }

    if ( $remember ) {
        $expiration = time() + apply_filters( 'auth_cookie_expiration', 1209600, $user_id, $remember ); // 2 weeks
        $expire     = $expiration;
    } else {
        $expiration = time() + apply_filters( 'auth_cookie_expiration', DAY_IN_SECONDS, $user_id, $remember ); // 1 day
        $expire     = 0;
    }

    $auth_cookie_name = AUTH_COOKIE;
    $scheme           = is_ssl() ? 'secure' : 'logged_in';
    $auth_cookie      = wp_generate_auth_cookie( $user_id, $expiration, $scheme, $token );
    $logged_in_cookie = wp_generate_auth_cookie( $user_id, $expiration, 'logged_in', $token );

    do_action( 'set_auth_cookie', $auth_cookie, $expire, $user_id, $auth_cookie_name, $scheme, $token );
    do_action( 'set_logged_in_cookie', $logged_in_cookie, $expire, $user_id, $auth_cookie_name, $scheme, $token );

    setcookie( $auth_cookie_name, $auth_cookie, $expire, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, $secure, true );
    setcookie( $auth_cookie_name, $auth_cookie, $expire, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure, true );
    setcookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, COOKIEPATH, COOKIE_DOMAIN, $secure, true );

    if ( COOKIEPATH != SITECOOKIEPATH ) {
        setcookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, $secure, true );
    }

    do_action( 'auth_cookie_created', $auth_cookie, $expire, $user_id, $auth_cookie_name, $scheme, $token );
}

函数参数详解:

参数 类型 描述
$user_id int 用户的 ID。这是必须的,WordPress 需要知道哪个用户需要被登录。
$remember bool 是否记住用户。如果设置为 true,Cookie 的过期时间将设置为两周(默认情况下)。如果设置为 false,Cookie 的过期时间将设置为一天(默认情况下)。
$secure mixed 是否仅通过 HTTPS 发送管理 Cookie。默认情况下,它基于 is_ssl() 函数。如果强制使用 HTTPS,则传递 true
$token string 会话令牌。用于增强安全性。如果提供了令牌,WordPress 会使用新的令牌重新生成 Cookie。

流程分析:Cookie 的诞生

  1. 确定 Cookie 的过期时间:

    根据 $remember 参数的值,确定 Cookie 的过期时间。如果 $remembertrue,则 Cookie 的过期时间通常设置为两周(1209600 秒)。如果 $rememberfalse,则 Cookie 的过期时间通常设置为一天(86400 秒)。当然,你可以使用 auth_cookie_expiration 过滤器来修改默认的过期时间。

    add_filter( 'auth_cookie_expiration', 'my_custom_cookie_expiration', 10, 3 );
    
    function my_custom_cookie_expiration( $expiration, $user_id, $remember ) {
        if ( $remember ) {
            // 记住我时,过期时间设置为一个月
            return 30 * DAY_IN_SECONDS;
        } else {
            // 不记住我时,过期时间设置为半天
            return 12 * HOUR_IN_SECONDS;
        }
    }
  2. 生成 Cookie 值:

    使用 wp_generate_auth_cookie() 函数生成 Cookie 的值。这个函数会根据用户 ID、过期时间、方案(logged_insecure)和令牌(如果提供)生成一个加密的字符串。

    function wp_generate_auth_cookie( $user_id, $expiration, $scheme = 'logged_in', $token = '' ) {
        $pass_frag = substr( wp_hash( get_user_field( 'user_pass', $user_id, 'raw' ), 'auth' ), 8, 4 );
    
        $key = wp_hash( $user_id . '|' . $pass_frag . '|' . $expiration . '|' . $scheme . '|' . $token, $scheme );
    
        $hash = hash_hmac( 'md5', $user_id . '|' . $expiration . '|' . $scheme . '|' . $token, $key );
    
        $auth_cookie = $user_id . '|' . $expiration . '|' . $hash . '|' . $scheme . '|' . $token;
    
        return $auth_cookie;
    }

    wp_generate_auth_cookie() 函数内部使用了很多加密算法,包括 wp_hash()hash_hmac(),以确保 Cookie 的安全性。

  3. 设置 Cookie:

    使用 setcookie() 函数将 Cookie 发送到用户的浏览器。wp_set_auth_cookie() 函数会设置两个主要的 Cookie:

    • AUTH_COOKIE: 用于管理后台的身份验证。
    • LOGGED_IN_COOKIE: 用于前台的身份验证。

    wp_set_auth_cookie() 函数会设置多个 Cookie,以确保在不同的路径下都能正常工作。例如,它会分别在 PLUGINS_COOKIE_PATHADMIN_COOKIE_PATH 下设置 AUTH_COOKIE

    setcookie( $auth_cookie_name, $auth_cookie, $expire, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, $secure, true );
    setcookie( $auth_cookie_name, $auth_cookie, $expire, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure, true );
    setcookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, COOKIEPATH, COOKIE_DOMAIN, $secure, true );
    
    if ( COOKIEPATH != SITECOOKIEPATH ) {
        setcookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, $secure, true );
    }
    • $auth_cookie_name: Cookie 的名称,通常是 wordpress_logged_in_[hash],其中 [hash] 是一个随机字符串。
    • $auth_cookie: Cookie 的值,由 wp_generate_auth_cookie() 函数生成。
    • $expire: Cookie 的过期时间,由 $remember 参数决定。
    • PLUGINS_COOKIE_PATHADMIN_COOKIE_PATH: Cookie 的路径,指定哪些路径下的页面可以访问该 Cookie。
    • COOKIE_DOMAIN: Cookie 的域名,指定哪些域名下的页面可以访问该 Cookie。
    • $secure: 是否仅通过 HTTPS 发送 Cookie。
    • true: 表示 Cookie 仅通过 HTTP 协议发送。

安全性考量:Cookie 的保护

WordPress 非常重视 Cookie 的安全性。它采取了多种措施来保护 Cookie 免受攻击:

  1. HTTPS:

    强烈建议使用 HTTPS 来保护 Cookie。当使用 HTTPS 时,Cookie 会被加密,防止被中间人窃取。$secure 参数用于指定是否仅通过 HTTPS 发送 Cookie。

  2. HTTPOnly:

    setcookie() 函数的最后一个参数设置为 true,表示 Cookie 仅通过 HTTP 协议发送。这意味着 JavaScript 无法访问 Cookie,从而防止跨站脚本攻击(XSS)。

  3. 加密:

    wp_generate_auth_cookie() 函数使用多种加密算法来生成 Cookie 的值,确保 Cookie 的内容无法被轻易破解。

  4. 密钥(Salts):

    WordPress 使用密钥(Salts)来增加 Cookie 的安全性。密钥是在 wp-config.php 文件中定义的随机字符串。它们用于加密 Cookie 的值,防止被恶意用户破解。

    define( 'AUTH_KEY',         'put your unique phrase here' );
    define( 'SECURE_AUTH_KEY',  'put your unique phrase here' );
    define( 'LOGGED_IN_KEY',    'put your unique phrase here' );
    define( 'NONCE_KEY',        'put your unique phrase here' );
    define( 'AUTH_SALT',        'put your unique phrase here' );
    define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
    define( 'LOGGED_IN_SALT',   'put your unique phrase here' );
    define( 'NONCE_SALT',       'put your unique phrase here' );
  5. 令牌(Token):

    $token 参数用于传递会话令牌。会话令牌可以增强安全性,防止会话劫持攻击。

Cookie 的验证:身份的确认

当用户再次访问网站时,浏览器会将 Cookie 发送给服务器。WordPress 会使用 wp_validate_auth_cookie() 函数来验证 Cookie 的有效性。

/**
 * Validates authentication cookie.
 *
 * @since 2.5.0
 *
 * @param string $cookie Optional authentication cookie. When empty, uses the 'wordpress_logged_in_' cookie.
 * @param string $scheme  Optional authentication scheme. Default 'logged_in'.
 * @return int|false User ID if the cookie is validated, false otherwise.
 */
function wp_validate_auth_cookie( $cookie = '', $scheme = 'logged_in' ) {
    if ( ! $cookie ) {
        switch ( $scheme ) {
            case 'auth':
                $cookie_name = AUTH_COOKIE;
                break;
            case 'secure_auth':
                $cookie_name = SECURE_AUTH_COOKIE;
                break;
            case 'logged_in':
            default:
                $cookie_name = LOGGED_IN_COOKIE;
        }

        if ( empty( $_COOKIE[ $cookie_name ] ) ) {
            return false;
        }

        $cookie = $_COOKIE[ $cookie_name ];
    }

    $cookie_elements = explode( '|', $cookie );
    if ( count( $cookie_elements ) !== 5 ) {
        return false;
    }

    list( $user_id, $expiration, $hmac, $scheme, $token ) = $cookie_elements;

    if ( ! is_numeric( $user_id ) ) {
        return false;
    }

    $user_id = (int) $user_id;

    /**
     * Fires before the authentication cookie is validated.
     *
     * @since 5.2.0
     *
     * @param array  $cookie_elements The parts of the authentication cookie.
     * @param string $cookie          The authentication cookie.
     * @param string $scheme          The scheme to use. 'auth', 'secure_auth', or 'logged_in'.
     */
    do_action( 'validate_auth_cookie', $cookie_elements, $cookie, $scheme );

    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
        if ( is_user_logged_in() ) {
            $current_user = wp_get_current_user();
            if ( $current_user && (int) $current_user->ID !== $user_id ) {
                error_log( 'WordPress cookie validation: Cookie user does not match logged in user.  WordPress is attempting to log in a user while a separate user is already logged in.  This is likely a bug.' );
            }
        }
    }

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

    $user = get_userdata( $user_id );
    if ( ! $user ) {
        return false;
    }

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

    $key = wp_hash( $user_id . '|' . $pass_frag . '|' . $expiration . '|' . $scheme . '|' . $token, $scheme );

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

    if ( ! hash_equals( $hmac, $hash ) ) {
        /**
         * Fires after an authentication cookie fails validation.
         *
         * @since 4.9.0
         *
         * @param array  $cookie_elements The parts of the authentication cookie.
         * @param string $cookie          The authentication cookie.
         * @param string $scheme          The scheme to use. 'auth', 'secure_auth', or 'logged_in'.
         */
        do_action( 'auth_cookie_malformed', $cookie_elements, $cookie, $scheme );
        return false;
    }

    /**
     * Fires after the authentication cookie is validated.
     *
     * @since 5.2.0
     *
     * @param array  $cookie_elements The parts of the authentication cookie.
     * @param string $cookie          The authentication cookie.
     * @param string $scheme          The scheme to use. 'auth', 'secure_auth', or 'logged_in'.
     * @param WP_User $user            The WP_User object if the cookie is validated.
     */
    do_action( 'auth_cookie_valid', $cookie_elements, $cookie, $scheme, $user );

    return $user_id;
}

函数参数详解:

参数 类型 描述
$cookie string 可选的身份验证 Cookie。如果为空,则使用 ‘wordpress_logged_in_’ Cookie。
$scheme string 可选的身份验证方案。默认为 ‘logged_in’。可以是 ‘auth’、’secure_auth’ 或 ‘logged_in’。

流程分析:验证的步骤

  1. 获取 Cookie:

    如果 $cookie 参数为空,wp_validate_auth_cookie() 函数会根据 $scheme 参数的值,从 $_COOKIE 数组中获取相应的 Cookie。

  2. 解析 Cookie:

    wp_validate_auth_cookie() 函数会将 Cookie 的值分割成五个部分:用户 ID、过期时间、HMAC、方案和令牌。

  3. 验证用户 ID:

    wp_validate_auth_cookie() 函数会验证用户 ID 是否为数字,并且是否存在对应的用户。

  4. 验证过期时间:

    wp_validate_auth_cookie() 函数会验证 Cookie 是否已过期。如果 Cookie 已过期,则验证失败。

  5. 验证 HMAC:

    wp_validate_auth_cookie() 函数会使用密钥和加密算法重新计算 HMAC,并与 Cookie 中的 HMAC 进行比较。如果两个 HMAC 不一致,则验证失败。

  6. 返回用户 ID:

    如果所有验证都通过,wp_validate_auth_cookie() 函数会返回用户 ID。否则,返回 false

退出登录:Cookie 的销毁

当用户点击“退出登录”按钮时,WordPress 会使用 wp_clear_auth_cookie() 函数来清除 Cookie。

/**
 * Logs the user out.
 *
 * @since 2.5.0
 */
function wp_clear_auth_cookie() {
    do_action( 'clear_auth_cookie' );

    $auth_cookie_name = AUTH_COOKIE;

    setcookie( $auth_cookie_name, ' ', time() - YEAR_IN_SECONDS, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN );
    setcookie( $auth_cookie_name, ' ', time() - YEAR_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN );
    setcookie( LOGGED_IN_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
    setcookie( LOGGED_IN_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN );

    $_COOKIE[ AUTH_COOKIE ]     = '';
    $_COOKIE[ SECURE_AUTH_COOKIE ] = '';
    $_COOKIE[ LOGGED_IN_COOKIE ]   = '';

    do_action( 'auth_cookie_cleared' );
}

wp_clear_auth_cookie() 函数会将 Cookie 的值设置为空字符串,并将过期时间设置为过去的时间,从而使 Cookie 失效。

总结:Cookie 的一生

  1. 登录: 用户输入用户名和密码,点击“登录”按钮。
  2. 验证: WordPress 验证用户的身份。
  3. 设置 Cookie: WordPress 使用 wp_set_auth_cookie() 函数在用户的浏览器中设置 Cookie。
  4. 后续访问: 用户再次访问网站时,浏览器会将 Cookie 发送给服务器。
  5. 验证 Cookie: WordPress 使用 wp_validate_auth_cookie() 函数验证 Cookie 的有效性。
  6. 自动登录: 如果 Cookie 验证成功,WordPress 会自动登录用户。
  7. 退出登录: 用户点击“退出登录”按钮。
  8. 清除 Cookie: WordPress 使用 wp_clear_auth_cookie() 函数清除 Cookie。

最佳实践:安全至上

  1. 始终使用 HTTPS: 确保你的网站使用 HTTPS 协议。
  2. 定期更新 WordPress 和插件: 及时更新 WordPress 和插件,以修复安全漏洞。
  3. 使用强密码: 鼓励用户使用强密码。
  4. 启用双因素认证: 启用双因素认证可以增加账户的安全性。
  5. 监控网站安全: 定期监控网站安全,及时发现和处理安全问题。

进阶话题:Session Tokens

WordPress 4.0 引入了 Session Tokens,进一步增强了 Cookie 的安全性。Session Tokens 的主要作用是防止会话劫持攻击。

当用户登录时,WordPress 会生成一个唯一的 Session Token,并将其存储在数据库中。同时,WordPress 会将 Session Token 嵌入到 Cookie 中。每次用户访问网站时,WordPress 都会验证 Cookie 中的 Session Token 是否与数据库中的 Session Token 匹配。如果匹配,则表示用户身份有效。如果不匹配,则表示可能存在会话劫持攻击。

如果检测到会话劫持攻击,WordPress 会立即注销用户,并生成一个新的 Session Token。

自定义登录:扩展的可能性

WordPress 提供了强大的钩子和过滤器,允许你自定义登录过程。例如,你可以使用 authenticate 过滤器来修改身份验证逻辑,或者使用 set_auth_cookie 动作来在 Cookie 设置后执行自定义操作。

add_filter( 'authenticate', 'my_custom_authentication', 30, 3 );

function my_custom_authentication( $user, $username, $password ) {
    // 自定义身份验证逻辑
    if ( $username == 'admin' && $password == 'password' ) {
        $user = get_user_by( 'login', $username );
    } else {
        $user = new WP_Error( 'authentication_failed', 'Invalid username or password.' );
    }

    return $user;
}

总结的总结:Cookie 的重要性

wp_set_auth_cookie() 函数是 WordPress 实现持久化登录的关键。理解它的工作原理,可以帮助你更好地保护你的 WordPress 网站的安全,并进行自定义登录。

好了,今天的讲座就到这里。希望大家有所收获!下次再见!

发表回复

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