Nonce 的秘密:wp_verify_nonce()
函数深度解析
大家好!我是今天的主讲人,很高兴能和大家一起探索 WordPress 世界里一个既神秘又重要的概念:Nonce。
你可能听过 Nonce,也可能只是在调试 WordPress 时偶然瞥见过它。但你真的理解它背后的原理吗?今天,我们就来扒一扒 wp_verify_nonce()
函数的源码,看看它是如何像一位严谨的保安,通过重新生成 Nonce 并进行对比,来验证其有效性的。
Nonce 是什么?为什么要用它?
首先,我们来聊聊什么是 Nonce。Nonce,全称 "Number used once",顾名思义,就是一个只能使用一次的随机数。在 WordPress 中,它被用来防止 CSRF (Cross-Site Request Forgery,跨站请求伪造) 攻击。
想象一下,如果没有 Nonce,黑客可以伪造一个请求,比如让你不小心点击一个链接,然后你的博客就自动删除了一篇文章。这听起来是不是很可怕?
Nonce 的作用就像给每个请求加上一个唯一的 "签名",只有拥有正确 "签名" 的请求才能被执行。这样,即使黑客伪造了请求,由于他不知道正确的 Nonce 值,请求也会被 WordPress 拒绝。
wp_verify_nonce()
函数:Nonce 的守门员
wp_verify_nonce()
函数就是负责验证 Nonce 是否有效的 "守门员"。它的主要任务是:
- 接收一个 Nonce 值,以及一个可选的动作 (action)。
- 根据 action 和当前用户的信息,重新生成一个 Nonce。
- 将接收到的 Nonce 值与重新生成的 Nonce 值进行对比。
- 如果两个 Nonce 值匹配,则认为 Nonce 有效;否则,认为 Nonce 无效。
源码剖析:wp_verify_nonce()
的内部运作
现在,让我们深入 wp_verify_nonce()
函数的源码,看看它是如何工作的。
function wp_verify_nonce( $nonce, $action = -1 ) {
$nonce = (string) $nonce;
$action = (string) $action;
$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;
}
// Invalid nonce
return false;
}
让我们逐行解读这段代码:
-
类型转换:
$nonce = (string) $nonce; $action = (string) $action;
首先,函数将接收到的 Nonce 和 action 转换为字符串类型,以确保后续操作的类型一致性。
-
获取 Nonce "时间片":
$i = wp_nonce_tick();
wp_nonce_tick()
函数返回一个基于时间的整数,我们称之为 "时间片"。它的作用是将 Nonce 的有效期限制在一个较短的时间范围内,通常是 12 小时。function wp_nonce_tick() { /** * Filters the duration of time that nonces are valid for. * * @since 2.5.0 * * @param int $lifespan Duration in seconds. */ $lifespan = apply_filters( 'nonce_life', DAY_IN_SECONDS ); return ceil( time() / ( $lifespan / 2 ) ); }
wp_nonce_tick()
的核心在于计算time() / ( $lifespan / 2 )
,其中time()
是当前时间戳,$lifespan
默认是DAY_IN_SECONDS
(一天)。 也就是说,默认情况下,Nonce 的有效期是 12 小时。ceil()
函数则保证了结果是一个整数。这个整数会随着时间推移而变化,每隔 12 小时增加 1。 -
生成期望的 Nonce:
$expected = substr( wp_hash( $i . $action . get_current_user_id(), 'nonce' ), -12, 10 );
这行代码是生成 Nonce 的关键。它使用了
wp_hash()
函数,对以下三个元素进行哈希运算:$i
:当前 "时间片"。$action
:用户自定义的 action。get_current_user_id()
:当前用户的 ID。
wp_hash()
函数使用 WordPress 的密钥 (salt) 对这三个元素进行哈希运算,生成一个唯一的哈希值。然后,使用substr()
函数截取哈希值的最后 10 个字符,作为期望的 Nonce 值。为什么只截取最后 10 个字符?这是为了缩短 Nonce 的长度,提高性能。同时,由于哈希算法的特性,即使只截取一部分字符,也能保证 Nonce 的唯一性。
-
对比 Nonce:
if ( hash_equals( $expected, $nonce ) ) { return 1; }
hash_equals()
函数用于安全地比较两个字符串。它能够防止时序攻击 (timing attack),保证比较的安全性。如果接收到的 Nonce 值与期望的 Nonce 值匹配,则函数返回1
,表示 Nonce 有效。 -
检查过期 Nonce:
$expected = substr( wp_hash( ( $i - 1 ) . $action . get_current_user_id(), 'nonce' ), -12, 10 ); if ( hash_equals( $expected, $nonce ) ) { return 2; }
为了处理一些网络延迟或用户时钟不准确的情况,函数还会检查 Nonce 是否是前一个 "时间片" 生成的。如果 Nonce 是前一个 "时间片" 生成的,则函数返回
2
,表示 Nonce 有效,但已经过期。 -
Nonce 无效:
return false;
如果接收到的 Nonce 值与当前 "时间片" 和前一个 "时间片" 生成的 Nonce 值都不匹配,则函数返回
false
,表示 Nonce 无效。
Nonce 的生命周期
Nonce 的生命周期可以简单概括为以下几个阶段:
- 生成: 使用
wp_create_nonce()
函数生成 Nonce。 - 嵌入: 将 Nonce 嵌入到 HTML 表单或 URL 中。
- 提交: 用户提交表单或点击 URL。
- 验证: 使用
wp_verify_nonce()
函数验证 Nonce 的有效性。 - 使用: 如果 Nonce 有效,则执行相应的操作。
示例代码:生成和验证 Nonce
下面是一个简单的示例,演示如何生成和验证 Nonce:
<?php
// 生成 Nonce
$nonce = wp_create_nonce( 'my_action' );
// 生成包含 Nonce 的 URL
$url = admin_url( 'admin-ajax.php?action=my_ajax_function&_wpnonce=' . $nonce );
echo '<a href="' . esc_url( $url ) . '">Click me!</a>';
// 在 Ajax 函数中验证 Nonce
add_action( 'wp_ajax_my_ajax_function', 'my_ajax_function' );
function my_ajax_function() {
// 验证 Nonce
if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'my_action' ) ) {
wp_die( 'Invalid Nonce!' );
}
// 执行操作
echo 'Nonce is valid!';
wp_die();
}
?>
在这个例子中,我们首先使用 wp_create_nonce()
函数生成一个 Nonce,并将其嵌入到一个 URL 中。然后,在 Ajax 函数 my_ajax_function()
中,我们使用 wp_verify_nonce()
函数验证 Nonce 的有效性。如果 Nonce 有效,则执行相应的操作。
wp_create_nonce()
函数:Nonce 的制造者
function wp_create_nonce( $action = -1 ) {
$action = (string) $action;
$i = wp_nonce_tick();
return substr( wp_hash( $i . $action . get_current_user_id(), 'nonce' ), -12, 10 );
}
这个函数是不是看起来很眼熟?没错,它和 wp_verify_nonce()
函数中生成 Nonce 的部分几乎一模一样! 这也很好理解,生成和验证 Nonce 的算法必须保持一致,才能保证 Nonce 的有效性。
总结:Nonce 的重要性
Nonce 是 WordPress 中一个重要的安全机制,它可以有效地防止 CSRF 攻击。通过理解 wp_verify_nonce()
函数的源码,我们可以更好地理解 Nonce 的工作原理,从而更好地保护我们的 WordPress 网站。
常见问题解答
-
Nonce 的有效期是多久?
默认情况下,Nonce 的有效期是 12 小时。可以通过
nonce_life
过滤器来修改 Nonce 的有效期。 -
Nonce 可以重复使用吗?
Nonce 的设计理念是 "Number used once",因此,Nonce 不应该重复使用。每次生成一个表单或 URL 时,都应该生成一个新的 Nonce。
-
如何选择合适的 action?
Action 应该能够唯一地标识一个操作。例如,可以使用 "delete_post_" . $post_id 来标识删除文章的操作。
-
为什么需要使用
hash_equals()
函数来比较 Nonce?hash_equals()
函数可以防止时序攻击,保证比较的安全性。 -
如果 Nonce 验证失败,应该怎么办?
如果 Nonce 验证失败,应该拒绝执行相应的操作,并向用户显示一个错误信息。
表格总结
函数 | 作用 |
---|---|
wp_create_nonce() |
生成一个 Nonce。 |
wp_verify_nonce() |
验证 Nonce 的有效性。 |
wp_nonce_tick() |
返回一个基于时间的整数,用于限制 Nonce 的有效期。 |
wp_hash() |
使用 WordPress 的密钥 (salt) 对数据进行哈希运算。 |
hash_equals() |
安全地比较两个字符串,防止时序攻击。 |
希望今天的分享能够帮助你更好地理解 WordPress 中的 Nonce 机制。记住,安全无小事,保护好你的网站,从理解 Nonce 开始!感谢大家的聆听,下课!