同学们,各位靓仔靓女,晚上好!今天咱们来聊聊 WordPress 中一个非常重要的安全机制:check_ajax_referer()
函数,特别是它如何验证 AJAX 请求中的 Nonce。这东西就像 AJAX 请求的通行证,没它,别想进门!
一、什么是 Nonce?为什么要用它?
首先,咱们得搞清楚 Nonce 是个啥玩意儿。Nonce,英文全称是 "Number used Once",顾名思义,就是“一次性使用的数字”。 在 WordPress 里,Nonce 可不是简单的数字,而是一个加密的字符串,用来防止 CSRF (Cross-Site Request Forgery,跨站请求伪造) 攻击。
CSRF 攻击是啥呢?简单来说,就是攻击者伪造你的身份,在未经你授权的情况下,执行某些操作。 举个例子,假设你登录了银行网站,正在浏览你的账户信息。 这时候,攻击者通过某种方式(比如邮件里的恶意链接),诱使你点击了一个链接,这个链接指向银行网站,并且包含了转账的请求。 如果银行网站没有采取 CSRF 防护措施,那么这个请求就有可能被执行,你的钱就被转走了!
Nonce 的作用就是,给每个请求加上一个只有你知道的“暗号”,服务器验证这个暗号,确认请求确实来自你的页面,而不是攻击者伪造的。
二、check_ajax_referer()
函数登场!
在 WordPress 的 AJAX 请求中,check_ajax_referer()
函数就是负责验证这个 Nonce 的大管家。 它的作用是:
- 接收 Nonce 值: 从 AJAX 请求中获取 Nonce 值。
- 验证 Nonce 值: 使用 WordPress 的内部函数,验证 Nonce 值是否有效。
- 如果验证失败: 优雅地拒绝请求,并可能返回错误信息。
三、check_ajax_referer()
的源码剖析
咱们来看看 check_ajax_referer()
的源码,看看它到底是怎么工作的。这个函数位于 wp-includes/pluggable.php
文件中(不同 WordPress 版本可能会略有差异,但核心逻辑不变)。
function check_ajax_referer( $action = -1, $query_arg = false, $die = true ) {
if ( -1 == $action ) {
_doing_it_wrong( __FUNCTION__, __( 'You should specify a nonce action to be verified.' ), '3.2' );
return false;
}
$query_arg = $query_arg ? $query_arg : '_wpnonce';
if ( isset( $_REQUEST[ $query_arg ] ) ) {
$nonce = $_REQUEST[ $query_arg ];
} else {
$nonce = '';
}
$result = wp_verify_nonce( $nonce, $action );
if ( false === $result && $die ) {
wp_nonce_ays( $action );
exit;
}
return $result;
}
让我们来逐行解读一下:
-
function check_ajax_referer( $action = -1, $query_arg = false, $die = true )
:- 定义了
check_ajax_referer()
函数,它接收三个参数:$action
:一个字符串,用于生成 Nonce。 这个参数非常重要,它决定了 Nonce 的唯一性。$query_arg
:可选参数,指定 Nonce 在请求中的参数名。 默认是'_wpnonce'
。$die
:可选参数,指定验证失败时是否立即终止脚本执行。 默认是true
。
- 定义了
-
if ( -1 == $action ) { ... }
:- 检查是否指定了
$action
。 如果没有指定,会触发一个_doing_it_wrong()
函数,提示开发者需要指定一个 Nonce action。
- 检查是否指定了
-
$query_arg = $query_arg ? $query_arg : '_wpnonce';
:- 如果
$query_arg
没有指定,则默认使用'_wpnonce'
作为参数名。
- 如果
-
if ( isset( $_REQUEST[ $query_arg ] ) ) { ... } else { ... }
:- 尝试从
$_REQUEST
超全局变量中获取 Nonce 值。$_REQUEST
包含了$_GET
、$_POST
和$_COOKIE
的数据。 - 如果找到了 Nonce,就赋值给
$nonce
变量; 否则,将$nonce
设置为空字符串。
- 尝试从
-
$result = wp_verify_nonce( $nonce, $action );
:- 调用
wp_verify_nonce()
函数来验证 Nonce 的有效性。 这是整个验证过程的核心!wp_verify_nonce()
函数会返回1
(有效)、2
(过期)或false
(无效)。
- 调用
-
if ( false === $result && $die ) { ... }
:- 如果验证失败(
$result
为false
),并且$die
参数为true
(默认值),则调用wp_nonce_ays()
函数显示一个确认页面,并终止脚本执行。
- 如果验证失败(
-
return $result;
:- 返回验证结果。
四、wp_verify_nonce()
函数:Nonce 验证的灵魂
现在,咱们把目光聚焦到 wp_verify_nonce()
函数,这个函数才是 Nonce 验证的灵魂所在。 它位于 wp-includes/pluggable.php
文件中。
function wp_verify_nonce( $nonce, $action = -1 ) {
$nonce = (string) $nonce;
$i = wp_nonce_tick();
// Nonce generated 0-12 hours ago
$expected = wp_hash( $i . $action . get_current_user_id(), 'nonce' );
if ( hash_equals( $expected, $nonce ) ) {
return 1;
}
// Nonce generated 12-24 hours ago
$expected = wp_hash( ( $i - 1 ) . $action . get_current_user_id(), 'nonce' );
if ( hash_equals( $expected, $nonce ) ) {
return 2;
}
return false;
}
咱们来一步步解析:
-
function wp_verify_nonce( $nonce, $action = -1 )
:- 定义了
wp_verify_nonce()
函数,它接收两个参数:$nonce
:要验证的 Nonce 值。$action
:用于生成 Nonce 的 Action 值。
- 定义了
-
$nonce = (string) $nonce;
:- 将
$nonce
强制转换为字符串类型。
- 将
-
$i = wp_nonce_tick();
:- 调用
wp_nonce_tick()
函数获取一个时间戳,这个时间戳会随着时间变化。
- 调用
-
$expected = wp_hash( $i . $action . get_current_user_id(), 'nonce' );
:- 使用
wp_hash()
函数,根据当前时间戳$i
、Action 值$action
和当前用户 ID 生成一个预期 Nonce 值。 wp_hash()
函数实际上是hash_hmac()
函数的封装,它使用 WordPress 的盐 (salt) 来增加安全性。
- 使用
-
if ( hash_equals( $expected, $nonce ) ) { ... }
:- 使用
hash_equals()
函数比较预期 Nonce 值$expected
和实际接收到的 Nonce 值$nonce
。 hash_equals()
函数可以防止时序攻击,即使攻击者知道 Nonce 的生成方式,也无法轻易伪造 Nonce。- 如果 Nonce 值匹配,则返回
1
,表示 Nonce 有效。
- 使用
-
$expected = wp_hash( ( $i - 1 ) . $action . get_current_user_id(), 'nonce' );
:- 如果第一次验证失败,则尝试使用前一个时间戳
$i - 1
重新生成预期 Nonce 值,并进行比较。 - 这是为了处理 Nonce 在时间间隔的边界情况下失效的问题。
- 如果第一次验证失败,则尝试使用前一个时间戳
-
if ( hash_equals( $expected, $nonce ) ) { ... }
:- 如果 Nonce 值匹配,则返回
2
,表示 Nonce 有效,但已经过期。
- 如果 Nonce 值匹配,则返回
-
return false;
:- 如果以上两次验证都失败,则返回
false
,表示 Nonce 无效。
- 如果以上两次验证都失败,则返回
五、wp_nonce_tick()
函数:时间戳的秘密
wp_nonce_tick()
函数负责生成一个随时间变化的时间戳,它位于 wp-includes/pluggable.php
文件中。
function wp_nonce_tick() {
/**
* Filters the lifespan of nonces.
*
* @since 4.5.0
*
* @param int $lifespan Nonce lifespan in seconds. Default is 12 hours.
*/
$nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS / 2 );
return ceil( time() / ( $nonce_life ) );
}
咱们来看一下:
-
function wp_nonce_tick() { ... }
:- 定义了
wp_nonce_tick()
函数。
- 定义了
-
$nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS / 2 );
:- 计算 Nonce 的有效期。 默认情况下,Nonce 的有效期是半天(12 小时)。
DAY_IN_SECONDS
是 WordPress 定义的一个常量,表示一天有多少秒 (60 * 60 * 24)。apply_filters( 'nonce_life', ... )
允许开发者通过nonce_life
过滤器修改 Nonce 的有效期。
-
return ceil( time() / ( $nonce_life ) );
:- 返回当前时间戳除以 Nonce 有效期,然后向上取整的结果。
- 这个结果会随着时间的推移而变化,从而保证 Nonce 的唯一性。
六、生成 Nonce:wp_create_nonce()
函数
既然咱们了解了如何验证 Nonce,那么如何生成 Nonce 呢? WordPress 提供了 wp_create_nonce()
函数,它位于 wp-includes/pluggable.php
文件中。
function wp_create_nonce( $action = -1 ) {
$i = wp_nonce_tick();
return wp_hash( $i . $action . get_current_user_id(), 'nonce' );
}
可以看到,wp_create_nonce()
函数的实现非常简单:
-
function wp_create_nonce( $action = -1 ) { ... }
:- 定义了
wp_create_nonce()
函数,它接收一个参数:$action
:用于生成 Nonce 的 Action 值。
- 定义了
-
$i = wp_nonce_tick();
:- 调用
wp_nonce_tick()
函数获取时间戳。
- 调用
-
return wp_hash( $i . $action . get_current_user_id(), 'nonce' );
:- 使用
wp_hash()
函数,根据时间戳$i
、Action 值$action
和当前用户 ID 生成 Nonce 值。
- 使用
七、实战演练:一个简单的 AJAX 请求
现在,咱们来用一个简单的例子来演示如何使用 Nonce 来保护 AJAX 请求。
1. 在 PHP 文件中生成 Nonce:
<?php
// 在 functions.php 或插件文件中
add_action( 'wp_ajax_my_ajax_action', 'my_ajax_callback' ); // 登录用户
add_action( 'wp_ajax_nopriv_my_ajax_action', 'my_ajax_callback' ); // 未登录用户
function my_ajax_callback() {
check_ajax_referer( 'my_nonce_action' ); // 验证 Nonce
// 处理 AJAX 请求的逻辑
$data = $_POST['data'];
$response = '接收到的数据:' . $data;
wp_send_json_success( $response );
}
function enqueue_my_scripts() {
wp_enqueue_script( 'my-ajax-script', get_template_directory_uri() . '/js/my-ajax-script.js', array( 'jquery' ), '1.0', true );
// 将 AJAX URL 和 Nonce 传递给 JavaScript
wp_localize_script( 'my-ajax-script', 'my_ajax_object', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'my_nonce_action' )
) );
}
add_action( 'wp_enqueue_scripts', 'enqueue_my_scripts' );
2. 在 JavaScript 文件中发送 AJAX 请求:
// js/my-ajax-script.js
jQuery(document).ready(function($) {
$('#my-button').click(function() {
var data = {
action: 'my_ajax_action',
data: 'Hello, world!',
_wpnonce: my_ajax_object.nonce // 从 wp_localize_script 传递过来的 Nonce
};
$.post(my_ajax_object.ajax_url, data, function(response) {
if (response.success) {
alert(response.data);
} else {
alert('AJAX 请求失败:' + response.data);
}
});
});
});
3. 在 HTML 文件中添加按钮:
<button id="my-button">发送 AJAX 请求</button>
在这个例子中:
'my_nonce_action'
是 Nonce 的 Action 值。wp_create_nonce( 'my_nonce_action' )
生成 Nonce。wp_localize_script()
函数将 AJAX URL 和 Nonce 传递给 JavaScript。- JavaScript 将 Nonce 作为
_wpnonce
参数发送到服务器。 check_ajax_referer( 'my_nonce_action' )
验证 Nonce。
八、总结与最佳实践
check_ajax_referer()
函数是 WordPress 中一个非常重要的安全机制,它可以有效地防止 CSRF 攻击。 在使用 AJAX 请求时,务必使用 Nonce 来保护你的数据。
以下是一些最佳实践:
- 为每个 AJAX 请求使用不同的 Action 值。 这样可以增加 Nonce 的唯一性,提高安全性。
- 定期更新 Nonce。 虽然 Nonce 有一定的有效期,但定期更新 Nonce 可以进一步提高安全性。
- 不要在客户端生成 Nonce。 客户端生成的 Nonce 容易被攻击者获取。
- 始终验证 Nonce。 即使你认为你的 AJAX 请求不重要,也应该验证 Nonce。
表格总结:
函数 | 作用 | 参数 | 返回值 |
---|---|---|---|
check_ajax_referer() |
验证 AJAX 请求中的 Nonce | $action (Action 值), $query_arg (Nonce 参数名), $die (验证失败是否终止脚本) |
1 (有效), 2 (过期), false (无效) |
wp_verify_nonce() |
验证 Nonce 的有效性 | $nonce (Nonce 值), $action (Action 值) |
1 (有效), 2 (过期), false (无效) |
wp_nonce_tick() |
生成随时间变化的时间戳 | 无 | 时间戳 |
wp_create_nonce() |
生成 Nonce | $action (Action 值) |
Nonce 值 |
wp_localize_script() |
将数据传递给 JavaScript | $handle (脚本句柄), $object_name (JavaScript 对象名), $l10n (数据数组) |
无 |
好了,今天的讲座就到这里。 希望大家能够理解 check_ajax_referer()
函数的原理和使用方法,并在实际开发中加以应用, 保护你的 WordPress 站点安全! 感谢大家的聆听!下次再见!