深入理解 WordPress `wp_enqueue_script()` 中的 `nonce` 参数源码:如何将 Nonce 传递给 JavaScript。

各位观众老爷,早上好! 欢迎来到今天的“WordPress秘籍:Nonce大法好”专场讲座。今天咱们不整虚的,直接扒光 wp_enqueue_script() 函数,看看它怎么把 Nonce 这玩意儿,神不知鬼不觉地塞给 JavaScript,让我们的代码更安全,更性感!

第一章:Nonce 是个啥?它为啥这么重要?

咱们先来聊聊 Nonce。 Nonce,这词儿听着挺高大上,其实就是“Number used Once”的缩写,也就是“一次性使用的数字”。它的作用简单粗暴:防止 CSRF (Cross-Site Request Forgery) 攻击。

CSRF 攻击是啥? 简单说,就是坏人冒充你,偷偷摸摸地干坏事。 比如,你登录了银行网站,坏人通过某种手段,让你不知情地点击了一个链接,这个链接实际上是向银行发出了一个转账请求,把你账户里的钱转走了! 是不是很可怕?

Nonce 就是用来解决这个问题的。 我们可以给每个敏感的操作,都加上一个 Nonce。 每次操作, Nonce 都不一样。 这样,坏人就算搞到了你的操作链接,因为没有 Nonce,或者 Nonce 不对,也无法冒充你进行操作。

举个栗子:

假设我们有个删除帖子的功能。 正常情况下,删除帖子的链接可能是这样的:

<a href="?action=delete_post&post_id=123">删除帖子</a>

如果坏人知道了这个链接,就可以构造一个假的请求,冒充你删除帖子。 但是,如果我们加上 Nonce:

<a href="?action=delete_post&post_id=123&_wpnonce=YOUR_UNIQUE_NONCE">删除帖子</a>

这样,坏人就算搞到了这个链接,没有正确的 _wpnonce 值,也无法删除帖子。 WordPress 会验证 _wpnonce 的值是否正确,如果不对,就拒绝执行删除操作。

第二章:wp_enqueue_script() 大法: 如何把 Nonce 塞进 JavaScript?

好,现在我们知道 Nonce 的重要性了。 那么,问题来了,我们怎么把 Nonce 传递给 JavaScript 呢? 总不能直接把 Nonce 写死在 JavaScript 文件里吧? 那样就太 Low 了,而且非常不安全。

WordPress 提供了 wp_enqueue_script() 函数,可以让我们加载 JavaScript 文件。 同时,它还提供了一个 wp_localize_script() 函数,可以让我们把 PHP 的数据传递给 JavaScript。 这两个函数配合使用,就可以完美地把 Nonce 传递给 JavaScript 了。

wp_enqueue_script() 的基本用法是这样的:

wp_enqueue_script(
    string   $handle,      // 脚本的唯一标识符
    string   $src = '',   // 脚本的 URL
    string[] $deps = [],    // 依赖的其他脚本
    string   $ver = false, // 脚本的版本号
    bool     $in_footer = false // 是否在页脚加载
);

wp_localize_script() 的基本用法是这样的:

wp_localize_script(
    string   $handle, // 脚本的唯一标识符 (必须和 wp_enqueue_script() 中的 $handle 一致)
    string   $object_name, // JavaScript 对象的名字
    array    $l10n // 要传递给 JavaScript 的数据
);

下面,我们来看一个完整的例子:

  1. 注册并加载 JavaScript 文件:
function my_enqueue_scripts() {
    wp_enqueue_script( 'my-custom-script', get_template_directory_uri() . '/js/my-script.js', array( 'jquery' ), '1.0', true );
}
add_action( 'wp_enqueue_scripts', 'my_enqueue_scripts' );

在这个例子中,我们注册了一个名为 my-custom-script 的 JavaScript 文件,它的 URL 是 get_template_directory_uri() . '/js/my-script.js',它依赖于 jquery,版本号是 1.0,并且在页脚加载。

  1. 生成 Nonce,并传递给 JavaScript:
function my_localize_script() {
    $nonce = wp_create_nonce( 'my_ajax_action' ); // 创建 Nonce
    wp_localize_script( 'my-custom-script', 'MyScriptData', array(
        'ajax_url' => admin_url( 'admin-ajax.php' ),
        'nonce' => $nonce
    ) );
}
add_action( 'wp_enqueue_scripts', 'my_localize_script' );

在这个例子中,我们首先使用 wp_create_nonce() 函数创建了一个 Nonce,它的 action 是 my_ajax_action。 然后,我们使用 wp_localize_script() 函数,把 Nonce 传递给 JavaScript。

  • 'my-custom-script' 是 JavaScript 文件的唯一标识符,必须和 wp_enqueue_script() 中的 $handle 一致。
  • 'MyScriptData' 是 JavaScript 对象的名字。 在 JavaScript 中,我们可以通过 MyScriptData.nonce 来访问 Nonce 的值。
  • array() 是要传递给 JavaScript 的数据。 在这个例子中,我们传递了两个数据: ajax_urlnonceajax_url 是 WordPress 的 AJAX 处理程序的 URL。 nonce 是我们刚刚创建的 Nonce。
  1. 在 JavaScript 中使用 Nonce:
jQuery(document).ready(function($) {
    $('#my-button').click(function() {
        $.ajax({
            url: MyScriptData.ajax_url, // 使用 MyScriptData.ajax_url
            type: 'POST',
            data: {
                action: 'my_ajax_action',
                nonce: MyScriptData.nonce, // 使用 MyScriptData.nonce
                post_id: 123
            },
            success: function(response) {
                alert(response);
            }
        });
    });
});

在这个例子中,我们在 JavaScript 中使用了 MyScriptData.ajax_urlMyScriptData.nonce。 当用户点击 #my-button 按钮时,我们会发起一个 AJAX 请求,把 actionnoncepost_id 发送到服务器。

  1. 在 PHP 中验证 Nonce:
function my_ajax_callback() {
    check_ajax_referer( 'my_ajax_action', 'nonce' ); // 验证 Nonce

    $post_id = $_POST['post_id'];

    // 执行一些操作,比如删除帖子

    wp_send_json_success( '帖子删除成功!' );
}
add_action( 'wp_ajax_my_ajax_action', 'my_ajax_callback' );
add_action( 'wp_ajax_nopriv_my_ajax_action', 'my_ajax_callback' );

在这个例子中,我们使用 check_ajax_referer() 函数验证 Nonce。 如果 Nonce 不正确, check_ajax_referer() 函数会终止程序的执行,并返回一个错误信息。 如果 Nonce 正确,我们就可以执行一些操作,比如删除帖子。

  • 'my_ajax_action' 是 Nonce 的 action,必须和 wp_create_nonce() 中的 $action 一致。
  • 'nonce' 是我们在 JavaScript 中传递 Nonce 的键名,必须和 JavaScript 中的 data: { nonce: ... } 中的 nonce 一致。

第三章:wp_localize_script() 源码分析:它到底做了什么?

现在,我们已经学会了如何使用 wp_enqueue_script()wp_localize_script() 函数,把 Nonce 传递给 JavaScript。 但是, wp_localize_script() 函数到底做了什么呢? 让我们来扒一扒它的源码:

function wp_localize_script( $handle, $object_name, $l10n ) {
    global $wp_scripts;

    if ( ! is_a( $wp_scripts, 'WP_Scripts' ) ) {
        return false;
    }

    $wp_scripts->localize( $handle, $object_name, $l10n );

    return true;
}

这段代码很简单,它主要做了两件事:

  1. 检查 $wp_scripts 对象是否存在。 $wp_scripts 是一个全局对象,它负责管理所有的 JavaScript 文件。 如果 $wp_scripts 对象不存在,说明 WordPress 没有正确地加载 JavaScript 文件, wp_localize_script() 函数会直接返回 false
  2. 调用 $wp_scripts->localize() 方法。 $wp_scripts->localize() 方法才是真正把数据传递给 JavaScript 的方法。

让我们再来看看 $wp_scripts->localize() 方法的源码:

public function localize( $handle, $object_name, $l10n ) {
    if ( ! is_array( $l10n ) ) {
        return;
    }

    foreach ( (array) $l10n as $key => $value ) {
        if ( ! is_scalar( $value ) ) {
            continue;
        }

        $l10n[ $key ] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8' );
    }

    $script = "var $object_name = " . wp_json_encode( $l10n ) . ';';

    if ( ! empty( $this->registered[ $handle ]->extra['before'] ) ) {
        $script = preg_replace( '/^(.*)$/m', '$1', $script );
        $this->registered[ $handle ]->extra['before'][] = $script;
    } else {
        $this->registered[ $handle ]->add_data( 'before', $script );
    }
}

这段代码稍微复杂一点,它主要做了以下几件事:

  1. 检查 $l10n 是否是数组。 如果 $l10n 不是数组,说明传递的数据格式不正确, $wp_scripts->localize() 方法会直接返回。
  2. 解码 HTML 实体。 为了防止 XSS 攻击, WordPress 会对传递的数据进行 HTML 实体编码。 在 JavaScript 中使用这些数据之前,我们需要对它们进行解码。
  3. 使用 wp_json_encode() 函数把数据转换成 JSON 格式。 wp_json_encode() 函数是 WordPress 提供的一个函数,它可以把 PHP 的数据转换成 JSON 格式。
  4. 生成 JavaScript 代码。 wp_localize_script() 函数会生成一段 JavaScript 代码,这段代码会创建一个全局的 JavaScript 对象,并把数据赋值给这个对象。 例如,如果我们传递的数据是 array( 'nonce' => 'YOUR_UNIQUE_NONCE' ),那么 wp_localize_script() 函数会生成如下的 JavaScript 代码:
var MyScriptData = {"nonce":"YOUR_UNIQUE_NONCE"};
  1. 把 JavaScript 代码添加到 JavaScript 文件的 before 属性中。 wp_localize_script() 函数会把生成的 JavaScript 代码添加到 JavaScript 文件的 before 属性中。 当 WordPress 加载 JavaScript 文件时,它会首先执行 before 属性中的代码,然后再执行 JavaScript 文件中的代码。 这样,我们就可以在 JavaScript 文件中使用 MyScriptData 对象了。

第四章:实战演练:一个完整的 AJAX 例子

为了让大家更好地理解 Nonce 的用法,我们来看一个完整的 AJAX 例子。 假设我们要做一个点赞功能,用户可以点击一个按钮,给帖子点赞。

  1. HTML 代码:
<button id="like-button" data-post-id="<?php the_ID(); ?>">点赞</button>
<span id="like-count"><?php echo get_post_meta( get_the_ID(), 'like_count', true ) ?: 0; ?></span>
  1. JavaScript 代码:
jQuery(document).ready(function($) {
    $('#like-button').click(function() {
        var postId = $(this).data('post-id');
        $.ajax({
            url: MyScriptData.ajax_url,
            type: 'POST',
            data: {
                action: 'my_like_post',
                nonce: MyScriptData.nonce,
                post_id: postId
            },
            success: function(response) {
                if (response.success) {
                    $('#like-count').text(response.data.like_count);
                } else {
                    alert(response.data.message);
                }
            }
        });
    });
});
  1. PHP 代码:
function my_enqueue_scripts() {
    wp_enqueue_script( 'my-like-script', get_template_directory_uri() . '/js/like-script.js', array( 'jquery' ), '1.0', true );
}
add_action( 'wp_enqueue_scripts', 'my_enqueue_scripts' );

function my_localize_script() {
    $nonce = wp_create_nonce( 'my_like_post' );
    wp_localize_script( 'my-like-script', 'MyScriptData', array(
        'ajax_url' => admin_url( 'admin-ajax.php' ),
        'nonce' => $nonce
    ) );
}
add_action( 'wp_enqueue_scripts', 'my_localize_script' );

function my_like_post_callback() {
    check_ajax_referer( 'my_like_post', 'nonce' );

    $post_id = $_POST['post_id'];
    $like_count = get_post_meta( $post_id, 'like_count', true ) ?: 0;
    $like_count++;
    update_post_meta( $post_id, 'like_count', $like_count );

    wp_send_json_success( array(
        'like_count' => $like_count
    ) );
}
add_action( 'wp_ajax_my_like_post', 'my_like_post_callback' );
add_action( 'wp_ajax_nopriv_my_like_post', 'my_like_post_callback' );

在这个例子中,我们首先创建了一个点赞按钮,当用户点击这个按钮时,我们会发起一个 AJAX 请求,把 actionnoncepost_id 发送到服务器。 在服务器端,我们首先验证 Nonce,如果 Nonce 正确,我们就更新帖子的点赞数,并返回最新的点赞数。

第五章:总结与注意事项

今天,我们深入学习了 WordPress 中 wp_enqueue_script()wp_localize_script() 函数的用法,以及如何使用 Nonce 来保护我们的代码。 在使用 Nonce 时,需要注意以下几点:

  • Nonce 的 action 必须唯一。 不同的操作应该使用不同的 action。
  • Nonce 的有效期有限。 默认情况下, Nonce 的有效期是 12 个小时。
  • Nonce 只能使用一次。 每次操作都应该生成一个新的 Nonce。
  • 在验证 Nonce 之前,一定要先验证用户是否具有执行该操作的权限。 即使 Nonce 正确,如果用户没有权限执行该操作,我们也应该拒绝执行。

表格总结:

函数/方法 作用 参数

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注