各位好,今天咱们来聊聊WordPress里一个看似简单,实则暗藏玄机的函数:wp_verify_nonce()
。别看它名字里有个“verify”(验证),实际上它背后的逻辑可比表面功夫复杂多了。咱们的目标是:把这玩意儿扒个精光,让你以后再看到它,就像看到老朋友一样亲切。
开场白:Nonce是个啥?为啥要验证?
在深入源码之前,咱们先搞清楚Nonce是啥。Nonce,全称Number used once,顾名思义,就是“一次性使用的数字”。在WordPress里,它被用来防止CSRF(Cross-Site Request Forgery,跨站请求伪造)攻击。
想象一下,你在银行网站登录了,然后坏人发给你一个链接,点进去后,悄悄地以你的名义转账。如果没有Nonce,银行服务器就没法区分这个请求是不是你亲自发起的。Nonce就像一个暗号,只有你和服务器知道,每次请求都要带上这个暗号,服务器才能确认这个请求是你本人发起的。
wp_verify_nonce()
函数的作用,就是验证这个“暗号”是不是有效。如果有效,说明请求很可能是合法的;如果无效,那就很有可能是CSRF攻击,果断拒绝!
源码解剖:一步一步走进wp_verify_nonce()
好了,理论知识铺垫完毕,现在咱们深入到wp_verify_nonce()
的源码里,看看它到底是怎么工作的。
首先,我们找到这个函数的定义(通常在wp-includes/pluggable.php
文件中):
function wp_verify_nonce( $nonce, $action = -1 ) {
$nonce = (string) $nonce;
$action = (string) $action;
$nonce_tick = wp_nonce_tick();
// Nonce generated 0-12 hours ago
$expected = substr( wp_hash( $nonce_tick . '|' . $action . '|' . get_current_user_id() . '|' . wp_get_session_token(), 'nonce' ), -12, 10 );
if ( hash_equals( $expected, $nonce ) ) {
return 1;
}
// Nonce generated 12-24 hours ago
$expected = substr( wp_hash( ( $nonce_tick - 1 ) . '|' . $action . '|' . get_current_user_id() . '|' . wp_get_session_token(), 'nonce' ), -12, 10 );
if ( hash_equals( $expected, $nonce ) ) {
return 2;
}
return false;
}
别被这一堆代码吓到,咱们一步一步来分析。
- 参数转换:
$nonce = (string) $nonce;
$action = (string) $action;
这两行代码很简单,就是把传入的$nonce
和$action
参数强制转换为字符串类型。这样做是为了防止类型不一致导致的问题。
- 获取Nonce的“时间片”:
$nonce_tick = wp_nonce_tick();
wp_nonce_tick()
函数是Nonce验证的关键。它会返回一个基于时间的数值,这个数值会影响Nonce的生成。我们来看看wp_nonce_tick()
的源码:
function wp_nonce_tick() {
/**
* Filters the lifespan of nonces.
*
* @since 4.5.0
*
* @param int $lifespan Nonce lifespan in seconds. Default 12 hours.
*/
$nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS / 2 );
return ceil( time() / ( $nonce_life ) );
}
这个函数的核心是time() / ( $nonce_life )
。time()
返回当前时间的Unix时间戳(从1970年1月1日到现在的秒数)。$nonce_life
默认是半天(12小时,DAY_IN_SECONDS / 2
)。所以,time() / ( $nonce_life )
的结果就是当前时间距离1970年1月1日有多少个12小时。ceil()
函数则对结果向上取整。
简单来说,wp_nonce_tick()
返回的是当前时间的12小时时间片。这意味着,在同一个12小时内生成的Nonce,wp_nonce_tick()
的返回值是相同的。
- 生成期望的Nonce:
$expected = substr( wp_hash( $nonce_tick . '|' . $action . '|' . get_current_user_id() . '|' . wp_get_session_token(), 'nonce' ), -12, 10 );
这行代码是生成期望的Nonce的关键。我们来分解一下:
$nonce_tick . '|' . $action . '|' . get_current_user_id() . '|' . wp_get_session_token()
: 这部分是将$nonce_tick
(时间片)、$action
(动作名称)、get_current_user_id()
(当前用户ID)和wp_get_session_token()
(会话令牌)拼接成一个字符串,用|
分隔。get_current_user_id()
返回当前登录用户的ID,如果用户未登录,则返回0。wp_get_session_token()
返回当前用户的会话令牌,这个令牌在用户登录时生成,用于识别用户。wp_hash(..., 'nonce')
:wp_hash()
函数使用WordPress的哈希算法对拼接的字符串进行哈希。第二个参数'nonce'
是一个“算法名称”,它会影响哈希算法的选择。WordPress会根据这个名称选择合适的哈希算法。substr(..., -12, 10)
:substr()
函数从哈希后的字符串中截取最后12位,然后取前10位作为最终的$expected
值。这样做是为了缩短Nonce的长度,同时增加破解的难度。
- 对比Nonce:
if ( hash_equals( $expected, $nonce ) ) {
return 1;
}
这行代码使用 hash_equals()
函数来比较期望的Nonce $expected
和传入的Nonce $nonce
是否相等。hash_equals()
函数是一个安全字符串比较函数,可以防止时序攻击。如果两个Nonce相等,说明验证通过,函数返回1。
- 检查前一个时间片:
$expected = substr( wp_hash( ( $nonce_tick - 1 ) . '|' . $action . '|' . get_current_user_id() . '|' . wp_get_session_token(), 'nonce' ), -12, 10 );
if ( hash_equals( $expected, $nonce ) ) {
return 2;
}
这部分代码是为了处理Nonce过期的情况。由于$nonce_tick
是基于12小时的时间片,如果用户在某个时间片内生成了Nonce,但是提交请求的时间跨越了两个时间片,那么第一次验证就会失败。为了解决这个问题,wp_verify_nonce()
还会检查前一个时间片生成的Nonce是否有效。如果前一个时间片的Nonce有效,函数返回2。
- 验证失败:
return false;
如果以上所有验证都失败了,说明Nonce无效,函数返回false
。
总结:wp_verify_nonce()
的验证流程
为了更清晰地理解wp_verify_nonce()
的验证流程,我们可以用一张表格来总结:
步骤 | 描述 | 涉及的变量 |
---|---|---|
1 | 获取当前时间的12小时时间片。 | $nonce_tick |
2 | 使用当前时间片、动作名称、用户ID和会话令牌生成期望的Nonce。 | $expected , $nonce_tick , $action , get_current_user_id() , wp_get_session_token() |
3 | 比较期望的Nonce和传入的Nonce是否相等。 | $expected , $nonce |
4 | 如果相等,验证通过,返回1。 | 无 |
5 | 如果不相等,使用前一个时间片、动作名称、用户ID和会话令牌生成期望的Nonce。 | $expected , $nonce_tick , $action , get_current_user_id() , wp_get_session_token() |
6 | 比较期望的Nonce和传入的Nonce是否相等。 | $expected , $nonce |
7 | 如果相等,验证通过,返回2。 | 无 |
8 | 如果以上所有验证都失败,验证失败,返回false 。 |
无 |
实战演练:如何正确使用wp_verify_nonce()
光说不练假把式,现在咱们来演示一下如何正确使用wp_verify_nonce()
。
首先,在你的表单中添加一个Nonce字段:
<form method="post" action="">
<?php wp_nonce_field( 'my_action', 'my_nonce' ); ?>
<input type="text" name="my_field">
<input type="submit" value="提交">
</form>
wp_nonce_field()
函数会自动生成一个隐藏的Nonce字段。它的第一个参数是$action
,也就是动作名称,第二个参数是Nonce字段的名称。
然后,在你的处理表单的PHP代码中,验证Nonce:
if ( isset( $_POST['my_nonce'] ) ) {
if ( wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) {
// Nonce验证通过,处理表单数据
$my_field = sanitize_text_field( $_POST['my_field'] );
echo '你输入的内容是:' . $my_field;
} else {
// Nonce验证失败,拒绝处理
echo '非法请求!';
}
}
这段代码首先检查$_POST
数组中是否存在名为my_nonce
的字段。如果存在,就调用wp_verify_nonce()
函数来验证Nonce。如果验证通过,就处理表单数据;如果验证失败,就拒绝处理。
注意事项:
$action
参数必须和生成Nonce时使用的$action
参数一致。- Nonce字段的名称必须和表单中的名称一致。
- 在处理表单数据之前,一定要先验证Nonce。
wp_verify_nonce()
函数只能验证一次。如果同一个Nonce被多次使用,第二次验证将会失败。
安全性考量:
虽然Nonce可以有效地防止CSRF攻击,但是它并不是万能的。以下是一些需要注意的安全问题:
- Nonce的泄露: 如果Nonce被泄露,攻击者就可以利用这个Nonce来发起CSRF攻击。因此,Nonce应该被保密,不要在URL中传递Nonce。
- 时间窗口: Nonce的有效期是有限的(默认是12小时)。如果用户在Nonce过期后提交请求,验证将会失败。因此,应该尽量缩短Nonce的有效期。
- 服务器时间同步:
wp_nonce_tick()
函数依赖于服务器的时间。如果服务器的时间不准确,可能会导致Nonce验证失败。因此,应该确保服务器的时间是准确的。
高级技巧:自定义Nonce的有效期
如果你觉得12小时的Nonce有效期太长或太短,你可以使用nonce_life
过滤器来自定义Nonce的有效期。例如,将Nonce的有效期设置为1小时:
add_filter( 'nonce_life', 'my_custom_nonce_life' );
function my_custom_nonce_life() {
return HOUR_IN_SECONDS; // 1小时
}
总结:
通过今天的讲解,相信大家对wp_verify_nonce()
函数有了更深入的理解。它不仅仅是一个简单的验证函数,而是WordPress安全体系中一个重要的组成部分。掌握它的原理和使用方法,可以帮助你编写更安全、更可靠的WordPress代码。记住,安全无小事,多一份防范,少一份风险。
希望今天的分享对你有所帮助!下次有机会,咱们再聊聊WordPress的其他有趣的技术细节。