咳咳,各位观众老爷们,晚上好!今天咱们来聊聊 WordPress 里一个挺重要的安全机制——check_ajax_referer()
,看看它是怎么帮我们验证 AJAX 请求中的“身份证明”的。
咱们先热热身:什么是 Nonce?
Nonce,这词儿听起来挺高大上,其实就是 "Number used once" 的缩写,翻译过来就是“一次性使用的数字”。 在 WordPress 里,它是一个随机生成的字符串,用来防止 CSRF(Cross-Site Request Forgery,跨站请求伪造)攻击。 简单来说,CSRF 攻击就是坏人冒充你偷偷地执行一些操作,比如偷偷发个帖子、偷偷改个密码啥的。
WordPress 使用 Nonce 来确保发起的请求确实来自你的网站,而不是其他地方伪造的。 想象一下,你的网站就像一个城堡,每个允许进入城堡的人都要有一张独特的通行证 (Nonce)。这张通行证只能用一次,用完就作废,下次来还得重新领一张。
check_ajax_referer()
的作用:守门大爷的职责
check_ajax_referer()
函数就像是城堡的守门大爷,专门负责检查 AJAX 请求里有没有携带正确的通行证 (Nonce)。 如果通行证不对,或者根本没有通行证,守门大爷就会毫不客气地把请求拒之门外。
check_ajax_referer()
的用法:怎么给 AJAX 请求加上通行证?
要使用 check_ajax_referer()
,通常需要两个步骤:
-
生成 Nonce: 在你的 PHP 代码里,用
wp_create_nonce()
函数生成一个 Nonce。 这个函数会返回一个随机字符串,你可以把它嵌入到你的 HTML 表单或者 JavaScript 代码里。<?php $nonce = wp_create_nonce( 'my_ajax_action' ); // 'my_ajax_action' 是一个唯一的 action 名称 ?>
这里的
'my_ajax_action'
就像是通行证的类型,不同的操作应该使用不同的 action 名称,这样才能更安全。 -
在 AJAX 请求中发送 Nonce: 把生成的 Nonce 放到 AJAX 请求的数据里,一起发送到服务器。
jQuery(document).ready(function($) { $('#my_button').click(function() { $.ajax({ url: ajaxurl, // WordPress 定义的 AJAX URL type: 'POST', data: { action: 'my_ajax_action', // PHP 函数处理 AJAX 请求的 action 名称 nonce: '<?php echo $nonce; ?>', // 传递生成的 Nonce some_data: '一些数据' }, success: function(response) { console.log(response); } }); }); });
-
验证 Nonce: 在你的 PHP 代码里,用
check_ajax_referer()
函数验证 AJAX 请求里携带的 Nonce 是否正确。<?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() { // 检查 Nonce check_ajax_referer( 'my_ajax_action', 'nonce' ); // 'my_ajax_action' 是 action 名称,'nonce' 是 AJAX 请求中 Nonce 的字段名 // 如果 Nonce 验证通过,就可以执行你的逻辑了 $data = $_POST['some_data']; $response = '你发送的数据是:' . $data; wp_send_json_success( $response ); // 返回 JSON 格式的成功响应 wp_die(); // 结束 AJAX 请求 } ?>
这里的
'my_ajax_action'
必须和生成 Nonce 时使用的 action 名称一致。'nonce'
是 AJAX 请求中 Nonce 字段的名称,通常是nonce
,但你也可以自定义。
深入 check_ajax_referer()
的源码:看看守门大爷是怎么工作的
check_ajax_referer()
函数实际上是对 wp_verify_nonce()
函数的一个封装,专门用于 AJAX 请求的 Nonce 验证。 让我们来扒一扒它的源码,看看守门大爷是怎么工作的:
function check_ajax_referer( $action = -1, $query_arg = false, $die = true ) {
$result = wp_verify_nonce( $_REQUEST[ $query_arg ], $action );
if ( false === $result ) {
if ( $die ) {
wp_die(
__( 'Are you sure you want to do this?' ),
__( 'Cheatin’ huh?' ),
array( 'response' => 403 )
);
}
return false;
}
return $result;
}
让我们一行一行地解读一下:
-
$result = wp_verify_nonce( $_REQUEST[ $query_arg ], $action );
:这行代码是核心。 它调用了wp_verify_nonce()
函数来验证 Nonce。$_REQUEST[ $query_arg ]
从$_REQUEST
数组中获取 Nonce 的值,$action
是 action 名称。$_REQUEST
包含了$_GET
、$_POST
和$_COOKIE
的内容。$query_arg
指的是 AJAX 请求中传递 Nonce 的参数名,默认为_wpnonce
,但是在check_ajax_referer
函数中,你可以指定这个参数名。 -
if ( false === $result ) { ... }
:如果wp_verify_nonce()
返回false
,说明 Nonce 验证失败。 -
if ( $die ) { ... }
:如果$die
参数为true
(默认值),wp_die()
函数会立即停止脚本的执行,并显示一个错误信息。wp_die()
就像一个急刹车,防止恶意请求继续执行。 -
wp_die( __( 'Are you sure you want to do this?' ), __( 'Cheatin’ huh?' ), array( 'response' => 403 ) );
:wp_die()
函数会显示一个错误信息,告诉用户:“你确定你要这么做吗?” 错误信息的标题是 “Cheatin’ huh?” (作弊,是吧?)。array( 'response' => 403 )
设置 HTTP 响应状态码为 403 (Forbidden,禁止访问)。 -
return false;
:如果$die
参数为false
,check_ajax_referer()
函数会返回false
,而不是直接停止脚本的执行。 这样,你就可以在你的代码里自定义错误处理逻辑。 -
return $result;
:如果 Nonce 验证通过,check_ajax_referer()
函数会返回wp_verify_nonce()
的返回值。wp_verify_nonce()
的返回值是 Nonce 的生成时间戳,可以用来判断 Nonce 是否过期。
wp_verify_nonce()
的源码:Nonce 验证的幕后英雄
wp_verify_nonce()
函数才是真正干活的,让我们继续深入,看看它是怎么验证 Nonce 的:
function wp_verify_nonce( $nonce, $action = -1 ) {
$nonce = (string) $nonce;
$i = wp_nonce_tick();
// Nonce generated 0-12 hours ago
$expected = substr( wp_hash( $i . $action . get_current_user_id(), 'nonce' ), -12, 10 );
if ( hash_equals( $expected, $nonce ) ) {
return 1;
}
// Nonce generated 12-24 hours ago
$expected = substr( wp_hash( ( $i - 1 ) . $action . get_current_user_id(), 'nonce' ), -12, 10 );
if ( hash_equals( $expected, $nonce ) ) {
return 2;
}
/**
* Fires when a nonce verification fails.
*
* @since 4.4.0
*
* @param string $nonce The nonce that was used.
* @param string|int $action The action that was used.
*/
do_action( 'wp_nonce_failed', $nonce, $action );
return false;
}
再来一行一行地解读一下:
-
$nonce = (string) $nonce;
:把 Nonce 转换成字符串类型,防止类型不匹配导致验证失败。 -
$i = wp_nonce_tick();
:调用wp_nonce_tick()
函数获取一个时间戳“刻度”。 这个刻度会根据时间变化,用于生成不同的 Nonce。 -
$expected = substr( wp_hash( $i . $action . get_current_user_id(), 'nonce' ), -12, 10 );
:这行代码生成一个“期望的” Nonce。 它把时间戳刻度$i
、action 名称$action
和当前用户 ID 连接起来,然后用wp_hash()
函数进行哈希运算,最后取哈希值的后 10 位作为“期望的” Nonce。 -
if ( hash_equals( $expected, $nonce ) ) { return 1; }
:这行代码比较“期望的” Nonce 和 AJAX 请求里携带的 Nonce 是否相等。hash_equals()
函数是一个安全比较函数,可以防止时序攻击。 如果 Nonce 相等,说明验证通过,返回1
。 -
$expected = substr( wp_hash( ( $i - 1 ) . $action . get_current_user_id(), 'nonce' ), -12, 10 );
:这行代码生成一个“旧的” Nonce。 它使用上一个时间戳刻度( $i - 1 )
来生成 Nonce。 这是为了防止 Nonce 过期太快,允许请求有一定的延迟。 -
if ( hash_equals( $expected, $nonce ) ) { return 2; }
:这行代码比较“旧的” Nonce 和 AJAX 请求里携带的 Nonce 是否相等。 如果 Nonce 相等,说明验证通过,返回2
。 -
do_action( 'wp_nonce_failed', $nonce, $action );
:如果 Nonce 验证失败,触发wp_nonce_failed
action,允许开发者执行一些自定义的逻辑,比如记录日志、发送邮件等等。 -
return false;
:如果 Nonce 验证失败,返回false
。
wp_nonce_tick()
的源码:时间戳刻度的秘密
wp_nonce_tick()
函数负责生成时间戳刻度,让我们看看它的源码:
function wp_nonce_tick() {
/**
* Filters the lifespan of nonces in seconds.
*
* @since 4.5.0
*
* @param int $lifespan Nonce lifespan in seconds. Default 12 hours.
*/
$lifespan = apply_filters( 'nonce_life', DAY_IN_SECONDS / 2 );
return ceil( time() / ( $lifespan ) );
}
-
$lifespan = apply_filters( 'nonce_life', DAY_IN_SECONDS / 2 );
:这行代码获取 Nonce 的有效期。 默认情况下,Nonce 的有效期是半天 (12 小时)。apply_filters()
函数允许开发者通过nonce_life
filter 来修改 Nonce 的有效期。 -
return ceil( time() / ( $lifespan ) );
:这行代码计算时间戳刻度。 它把当前时间戳除以 Nonce 的有效期,然后向上取整。 这样,每隔一段时间,时间戳刻度就会变化一次,从而生成新的 Nonce。
总结:Nonce 验证的流程
现在,我们来总结一下 Nonce 验证的整个流程:
-
生成 Nonce: 使用
wp_create_nonce()
函数生成一个 Nonce。 这个函数会调用wp_nonce_tick()
函数获取时间戳刻度,然后把时间戳刻度、action 名称和当前用户 ID 连接起来,进行哈希运算,最后取哈希值的后 10 位作为 Nonce。 -
在 AJAX 请求中发送 Nonce: 把生成的 Nonce 放到 AJAX 请求的数据里,一起发送到服务器。
-
验证 Nonce: 使用
check_ajax_referer()
函数验证 AJAX 请求里携带的 Nonce 是否正确。 这个函数会调用wp_verify_nonce()
函数来验证 Nonce。wp_verify_nonce()
函数会根据当前时间戳刻度和上一个时间戳刻度生成两个“期望的” Nonce,然后分别和 AJAX 请求里携带的 Nonce 进行比较。 如果 Nonce 匹配,说明验证通过;否则,验证失败。
表格总结
函数名 | 作用 | 参数 | 返回值 |
---|---|---|---|
wp_create_nonce() |
生成一个 Nonce | $action (string):action 名称,用于区分不同的操作 |
Nonce (string):随机生成的字符串 |
check_ajax_referer() |
验证 AJAX 请求中的 Nonce | $action (string):action 名称,必须和生成 Nonce 时使用的 action 名称一致;$query_arg (string):AJAX 请求中 Nonce 字段的名称,默认为 _wpnonce ;$die (bool):是否在验证失败时停止脚本的执行,默认为 true |
如果验证通过,返回 wp_verify_nonce() 的返回值 (1 或 2);如果验证失败,且 $die 为 true ,则停止脚本的执行并显示错误信息;如果验证失败,且 $die 为 false ,则返回 false |
wp_verify_nonce() |
验证 Nonce 是否有效 | $nonce (string):要验证的 Nonce;$action (string):action 名称,必须和生成 Nonce 时使用的 action 名称一致 |
如果验证通过,且 Nonce 是当前时间戳生成的,返回 1 ;如果验证通过,且 Nonce 是上一个时间戳生成的,返回 2 ;如果验证失败,返回 false |
wp_nonce_tick() |
获取时间戳刻度,用于生成 Nonce | 无 | 时间戳刻度 (int):根据当前时间戳和 Nonce 有效期计算出来的整数 |
一些需要注意的地方:
- Action 名称的唯一性: 不同的操作应该使用不同的 action 名称,这样可以防止 Nonce 被滥用。
- Nonce 的有效期: Nonce 的有效期默认是 12 小时,你可以通过
nonce_life
filter 来修改。 Nonce 的有效期不宜过长,否则容易被破解;也不宜过短,否则会影响用户体验。 - HTTPS: 如果你的网站使用了 HTTPS,可以防止中间人攻击,从而提高 Nonce 的安全性。
- 安全比较: 使用
hash_equals()
函数进行 Nonce 的比较,可以防止时序攻击。
举个栗子:评论功能的 Nonce 验证
假设你要给 WordPress 的评论功能添加 Nonce 验证,可以这样做:
-
生成 Nonce: 在
comment_form()
函数里,添加一个隐藏字段,用于存储 Nonce。<?php function my_comment_form_defaults( $defaults ) { $defaults['fields']['nonce'] = '<p class="comment-form-nonce"><input type="hidden" name="comment_nonce" value="' . wp_create_nonce( 'comment_nonce' ) . '" /></p>'; return $defaults; } add_filter( 'comment_form_defaults', 'my_comment_form_defaults' ); ?>
-
验证 Nonce: 在
wp_insert_comment()
函数执行之前,验证 Nonce 是否正确。<?php function my_pre_comment_on_post( $commentdata ) { if ( ! isset( $_POST['comment_nonce'] ) || ! wp_verify_nonce( $_POST['comment_nonce'], 'comment_nonce' ) ) { wp_die( 'Cheatin’ huh?' ); } return $commentdata; } add_filter( 'preprocess_comment', 'my_pre_comment_on_post' ); ?>
总结
check_ajax_referer()
函数是 WordPress 里一个非常重要的安全机制,它可以帮助我们防止 CSRF 攻击,保护我们的网站免受恶意请求的侵害。 理解它的工作原理,可以让我们更好地利用它,提高我们网站的安全性。
好了,今天的讲座就到这里,希望大家有所收获! 下次有机会再和大家分享其他的 WordPress 技术干货。 拜拜!