WordPress REST API 认证流程中 wp_rest_auth_cookie 验证机制的源码分析
大家好,今天我们来深入探讨 WordPress REST API 认证流程中一个非常重要的环节:wp_rest_auth_cookie
验证机制。这是一个基于 Cookie 的身份验证方式,常用于 WordPress 站点内部的 REST API 调用,例如 Gutenberg 编辑器、后台管理界面等等。我们将从源码层面剖析它的工作原理,理解其安全性以及适用场景。
1. Cookie 认证的基本概念
在深入源码之前,我们先简单回顾一下 Cookie 认证的基础概念。
- Cookie: 储存在用户浏览器上的小型文本文件,可以包含用户身份信息、会话信息等。
- Session: 服务器端维护的用户会话状态,通常与 Cookie 中的 Session ID 关联。
Cookie 认证的基本流程是:用户登录后,服务器生成一个 Session ID,并将它保存在 Cookie 中发送给浏览器。后续用户访问时,浏览器会自动携带这个 Cookie,服务器通过 Cookie 中的 Session ID 找到对应的 Session 信息,从而验证用户身份。
2. WordPress Cookie 认证的特殊性
WordPress 的 Cookie 认证机制与传统的 Session 认证有一些区别:
- 没有显式的 Session: WordPress 并没有像 PHP 的
$_SESSION
那样的全局 Session 对象。它的 Cookie 认证依赖于 WordPress 的用户数据和密钥。 - 密钥加盐: WordPress 使用
AUTH_KEY
、SECURE_AUTH_KEY
等常量作为密钥,并对密钥进行加盐处理,增强安全性。 - 插件扩展性: WordPress 提供了丰富的钩子(hooks)机制,允许插件扩展或修改 Cookie 认证流程。
3. wp_rest_auth_cookie
验证机制的入口:rest_cookie_check_errors()
rest_cookie_check_errors()
函数是 REST API 请求进行 Cookie 认证的入口点。它位于 wp-includes/rest-api/class-wp-rest-server.php
文件中。
/**
* Check if the user is logged in based on cookies.
*
* @since 4.7.0
*
* @param WP_Error|null $result Authentication result, null if not yet authenticated.
*
* @return WP_Error|true Authentication result.
*/
function rest_cookie_check_errors( $result ) {
if ( ! empty( $result ) ) {
return $result;
}
if ( ! is_user_logged_in() ) {
return new WP_Error(
'rest_not_logged_in',
__( 'You are not currently logged in.' ),
array( 'status' => 401 )
);
}
// Is the user logged in, but lacking the nonce?
if ( empty( $_COOKIE[ LOGGED_IN_COOKIE ] ) ) {
return new WP_Error(
'rest_cookie_invalid_nonce',
__( 'Cookie nonce is invalid' ),
array( 'status' => 403 )
);
}
return true;
}
这个函数主要做了以下几件事:
- 检查是否已经认证: 如果
$result
不为空,说明已经经过其他认证方式(例如 OAuth),直接返回$result
。 - 检查用户是否登录: 使用
is_user_logged_in()
函数检查用户是否已经登录。如果未登录,返回一个WP_Error
对象,表示认证失败。 - 检查 Cookie 是否存在: 检查
LOGGED_IN_COOKIE
常量对应的 Cookie 是否存在。如果不存在,返回一个WP_Error
对象,表示 Cookie 无效。
注意: LOGGED_IN_COOKIE
常量定义了 WordPress 用于存储登录信息的 Cookie 名称。它通常包含用户名、过期时间和一个哈希值。
4. is_user_logged_in()
函数的运作方式
is_user_logged_in()
函数是 WordPress 验证用户是否登录的关键函数。它位于 wp-includes/pluggable.php
文件中。
/**
* Determines whether the current visitor is a logged in user.
*
* @since 2.0.0
*
* @return bool True if the user is logged in, false if not.
*/
function is_user_logged_in() {
$user = wp_get_current_user();
return ( $user->exists() );
}
is_user_logged_in()
函数内部调用了 wp_get_current_user()
函数来获取当前用户对象。如果用户对象存在($user->exists()
返回 true),则认为用户已登录。
5. wp_get_current_user()
函数的深入分析
wp_get_current_user()
函数负责从 Cookie 中提取用户信息并验证其有效性。它位于 wp-includes/pluggable.php
文件中。
/**
* Retrieve the current user object.
*
* @since 2.0.0
* @since 3.5.0 Returns a WP_User object, or a WP_User object with ID 0 if no user is logged in.
*
* @return WP_User WP_User object
*/
function wp_get_current_user() {
static $current_user = null;
if ( ! isset( $current_user ) ) {
$current_user = wp_get_current_user_old();
}
return $current_user;
}
这个函数使用了静态变量 $current_user
来缓存用户对象,避免重复查询。它实际上调用了 wp_get_current_user_old()
函数来完成用户信息的提取和验证。
6. wp_get_current_user_old()
函数的详细过程
wp_get_current_user_old()
函数是 Cookie 认证的核心所在。它位于 wp-includes/pluggable.php
文件中。
function wp_get_current_user_old() {
global $wp_current_user;
if ( ! ( $wp_current_user instanceof WP_User ) ) {
$wp_current_user = new WP_User();
}
if ( defined( 'XMLRPC_REQUEST' ) || defined( 'REST_REQUEST' ) || ( defined( 'WP_INSTALLING' ) && WP_INSTALLING ) || is_feed() || defined( 'DOING_AJAX' ) ) {
return $wp_current_user;
}
if ( isset( $_COOKIE[ LOGGED_IN_COOKIE ] ) ) {
$logged_in_cookie = $_COOKIE[ LOGGED_IN_COOKIE ];
$cookie_elements = explode( '|', $logged_in_cookie );
if ( count( $cookie_elements ) === 4 ) {
list( $username, $expiration, $token, $hmac ) = $cookie_elements;
if ( $expiration > time() ) {
/**
* Fires before the authentication cookie is validated.
*
* @since 2.8.0
*
* @param string $username The username from the authentication cookie.
* @param string $token The token from the authentication cookie.
*/
do_action( 'auth_cookie_validation', $username, $token );
$user = get_user_by( 'login', $username );
if ( $user ) {
$pass_frag = substr( $user->user_pass, 8, 4 );
$key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, 'logged_in' );
$key = hash_hmac( 'md5', $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $key );
if ( hash_equals( $hmac, $key ) ) {
$wp_current_user = $user;
wp_set_auth_cookie( $user->ID, true, is_ssl() );
do_action( 'set_current_user' );
}
}
}
}
}
return $wp_current_user;
}
这个函数的过程比较复杂,我们逐步分析:
- 初始化用户对象: 如果
$wp_current_user
不是WP_User
类的实例,则创建一个新的WP_User
对象。 - 跳过 Cookie 认证: 在某些情况下(例如 XML-RPC 请求、REST 请求、安装过程、Feed 以及 AJAX 请求),会直接返回初始化的用户对象,跳过 Cookie 认证。注意:这里 REST_REQUEST 的定义会跳过cookie认证,但是实际上,
rest_cookie_check_errors
会先于此函数执行,所以会进行cookie检查。 - 检查 Cookie 是否存在: 检查
LOGGED_IN_COOKIE
常量对应的 Cookie 是否存在。如果不存在,直接返回初始化的用户对象。 - 解析 Cookie 值: 将 Cookie 值按照
|
分隔符拆分成多个元素。这些元素包括:用户名、过期时间、token 和 HMAC 值。 - 验证 Cookie 过期时间: 检查过期时间是否大于当前时间。如果已过期,直接返回初始化的用户对象。
- 执行
auth_cookie_validation
钩子: 触发auth_cookie_validation
钩子,允许插件在 Cookie 验证之前执行自定义逻辑。 - 根据用户名获取用户对象: 使用
get_user_by( 'login', $username )
函数根据用户名获取用户对象。 - 计算 HMAC 值: 使用密钥和加盐算法计算 HMAC 值,并与 Cookie 中的 HMAC 值进行比较。
- 获取密码片段: 从用户密码中截取一个片段(第 8 到 11 位)。
- 生成密钥: 使用用户名、密码片段、过期时间和 token,以及 ‘logged_in’ 字符串,通过
wp_hash()
函数生成密钥。 - 计算 HMAC: 使用 HMAC-MD5 算法对用户名、密码片段、过期时间和 token 进行哈希计算,密钥为上一步生成的密钥。
- 验证 HMAC 值: 使用
hash_equals()
函数比较计算出的 HMAC 值和 Cookie 中的 HMAC 值。hash_equals()
函数可以防止时序攻击。 - 设置当前用户: 如果 HMAC 值验证成功,则将获取到的用户对象设置为当前用户,并调用
wp_set_auth_cookie()
函数重新设置 Cookie。 - 执行
set_current_user
钩子: 触发set_current_user
钩子,允许插件在设置当前用户后执行自定义逻辑。
7. wp_set_auth_cookie()
函数的作用
wp_set_auth_cookie()
函数用于设置登录 Cookie。它位于 wp-includes/pluggable.php
文件中。
/**
* 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 bool $secure Whether the admin cookies should only be used over HTTPS.
*/
function wp_set_auth_cookie( $user_id, $remember = false, $secure = '' ) {
if ( '' === $secure ) {
$secure = is_ssl();
}
$expiration = time() + DAY_IN_SECONDS;
$rememberme_expiration = time() + ( $remember ? ( 14 * DAY_IN_SECONDS ) : DAY_IN_SECONDS );
if ( $remember ) {
$expiration = $rememberme_expiration;
}
$user = get_userdata( $user_id );
$username = $user->user_login;
/**
* Filters the authentication cookie expiration period.
*
* @since 2.8.0
*
* @param int $expiration Authentication cookie expiration period.
* @param int $user_id User ID.
* @param bool $remember Whether to remember the user. Default false.
* @param string $username User login.
*/
$expiration = apply_filters( 'auth_cookie_expiration', $expiration, $user_id, $remember, $username );
$token = wp_generate_password( 43, false, false );
update_user_meta( $user_id, 'session_tokens', wp_get_session_token( $user_id, $token ) );
$pass_frag = substr( $user->user_pass, 8, 4 );
$key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, 'logged_in' );
$hash = hash_hmac( 'md5', $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $key );
$auth_cookie = $username . '|' . $expiration . '|' . $token . '|' . $hash;
setcookie( LOGGED_IN_COOKIE, $auth_cookie, $expiration, COOKIEPATH, COOKIE_DOMAIN, $secure, true );
if ( COOKIEPATH != SITECOOKIEPATH ) {
setcookie( LOGGED_IN_COOKIE, $auth_cookie, $expiration, SITECOOKIEPATH, COOKIE_DOMAIN, $secure, true );
}
/**
* Fires after the authentication cookies are set.
*
* @since 2.5.0
*
* @param string $auth_cookie The authentication cookie.
* @param int $user_id User ID.
* @param bool $remember Whether to remember the user. Default false.
* @param string $username User login.
*/
do_action( 'set_auth_cookie', $auth_cookie, $user_id, $remember, $username );
}
这个函数主要做了以下几件事:
- 确定 Cookie 的安全标志: 根据是否使用 HTTPS 协议,确定 Cookie 的安全标志。
- 计算 Cookie 的过期时间: 根据是否选择“记住我”选项,计算 Cookie 的过期时间。
- 获取用户名: 根据用户 ID 获取用户名。
- 生成 token: 生成一个随机token, 并使用
update_user_meta
保存到数据库.wp_get_session_token
函数用于管理session token, 包括生成,存储,删除等操作。 - 计算 HMAC 值: 使用密钥和加盐算法计算 HMAC 值。
- 构建 Cookie 值: 将用户名、过期时间、token 和 HMAC 值组合成 Cookie 值。
- 设置 Cookie: 使用
setcookie()
函数设置 Cookie。 - 触发
set_auth_cookie
钩子: 触发set_auth_cookie
钩子,允许插件在设置 Cookie 后执行自定义逻辑。
8. wp_hash()
函数和密钥加盐
wp_hash()
函数是 WordPress 用于生成密钥的函数。它位于 wp-includes/pluggable.php
文件中。
/**
* Generates a hash (keyed HMAC) of a given string.
*
* @since 2.5.0
* @access private
*
* @param string $data The string to be hashed.
* @param string $scheme The cryptographic scheme to use.
* See hash_hmac() for list of supported schemes.
* @return string The hash.
*/
function wp_hash( $data, $scheme = 'auth' ) {
$salt = wp_salt( $scheme );
return hash_hmac( 'md5', $data, $salt );
}
wp_hash()
函数内部调用了 wp_salt()
函数来获取盐值,然后使用 HMAC-MD5 算法对数据进行哈希计算。
wp_salt()
函数负责生成盐值。它位于 wp-includes/pluggable.php
文件中。
/**
* Salts a string using the AUTH_KEY constant.
*
* @since 2.5.0
* @access private
*
* @param string $scheme Salt scheme.
* @return string
*/
function wp_salt( $scheme = 'auth' ) {
if ( defined( 'AUTH_KEY' ) ) {
switch ( $scheme ) {
case 'auth':
$salt = AUTH_KEY;
break;
case 'secure_auth':
$salt = SECURE_AUTH_KEY;
break;
case 'logged_in':
$salt = LOGGED_IN_KEY;
break;
case 'nonce':
$salt = NONCE_KEY;
break;
default:
$salt = AUTH_KEY;
break;
}
} else {
$salt = '';
}
return $salt;
}
wp_salt()
函数根据不同的方案(例如 ‘auth’、’secure_auth’、’logged_in’、’nonce’)返回不同的密钥。这些密钥定义在 wp-config.php
文件中,例如 AUTH_KEY
、SECURE_AUTH_KEY
、LOGGED_IN_KEY
、NONCE_KEY
和相应的 AUTH_SALT
、SECURE_AUTH_SALT
、LOGGED_IN_SALT
、NONCE_SALT
。
9. 安全性分析
wp_rest_auth_cookie
验证机制的安全性依赖于以下几个因素:
- 密钥的保密性:
AUTH_KEY
、SECURE_AUTH_KEY
等密钥必须保密,不能泄露给他人。 - HMAC 算法的强度: HMAC-MD5 算法虽然不是最强的哈希算法,但在密钥足够安全的前提下,可以提供一定的安全性。
hash_equals()
函数的防时序攻击能力:hash_equals()
函数可以防止时序攻击,避免攻击者通过测量哈希比较的时间来猜测密钥。- HTTPS 协议的使用: 使用 HTTPS 协议可以防止 Cookie 被中间人窃取。
10. 适用场景
wp_rest_auth_cookie
验证机制适用于以下场景:
- WordPress 站点内部的 REST API 调用: 例如 Gutenberg 编辑器、后台管理界面等等。
- 需要用户登录才能访问的 REST API 接口。
- 不需要高安全性的 REST API 接口。
对于需要高安全性的 REST API 接口,建议使用 OAuth 2.0 或 JWT 等更安全的认证方式。
11. 代码示例:如何使用 wp_rest_auth_cookie
进行认证
在 JavaScript 中,可以使用以下代码发送带有 Cookie 的 REST API 请求:
fetch( '/wp-json/my-plugin/v1/my-endpoint', {
credentials: 'include' // 关键:包含 Cookie
})
.then( response => response.json() )
.then( data => {
console.log( data );
});
关键在于 credentials: 'include'
选项。这个选项告诉浏览器在发送请求时包含 Cookie。
在 PHP 中,可以使用以下代码检查用户是否通过 Cookie 认证:
add_action( 'rest_api_init', function () {
register_rest_route( 'my-plugin/v1', '/my-endpoint', array(
'methods' => 'GET',
'callback' => 'my_endpoint_callback',
'permission_callback' => function () {
return is_user_logged_in(); // 关键:检查用户是否登录
}
) );
});
function my_endpoint_callback( WP_REST_Request $request ) {
// 处理请求
return array( 'message' => 'Hello, ' . wp_get_current_user()->user_login );
}
关键在于 permission_callback
函数。这个函数使用 is_user_logged_in()
函数检查用户是否登录。
12. 表格:wp_rest_auth_cookie
验证机制的关键函数和常量
函数/常量 | 作用 | 文件 |
---|---|---|
rest_cookie_check_errors() |
REST API 请求进行 Cookie 认证的入口点 | wp-includes/rest-api/class-wp-rest-server.php |
is_user_logged_in() |
检查用户是否已经登录 | wp-includes/pluggable.php |
wp_get_current_user() |
获取当前用户对象 | wp-includes/pluggable.php |
wp_get_current_user_old() |
从 Cookie 中提取用户信息并验证其有效性 | wp-includes/pluggable.php |
wp_set_auth_cookie() |
设置登录 Cookie | wp-includes/pluggable.php |
wp_hash() |
生成密钥 | wp-includes/pluggable.php |
wp_salt() |
生成盐值 | wp-includes/pluggable.php |
LOGGED_IN_COOKIE |
存储登录信息的 Cookie 名称 | wp-config.php |
AUTH_KEY |
用于生成密钥的常量 | wp-config.php |
SECURE_AUTH_KEY |
用于生成密钥的常量(用于 HTTPS 请求) | wp-config.php |
安全性与适用性: 选择合适的认证方式
我们深入剖析了 wp_rest_auth_cookie
验证机制的原理和实现,了解了它的优缺点以及适用场景。选择合适的认证方式,才能保证 WordPress REST API 的安全性和可用性。