Làm thế nào để có được một nonce duy nhất cho mỗi yêu cầu Ajax?


11

Tôi đã thấy một vài cuộc thảo luận về việc khiến Wordpress tạo lại một lần duy nhất cho các yêu cầu Ajax tiếp theo, nhưng đối với cuộc sống của tôi, tôi thực sự không thể khiến Wordpress làm điều đó-- mỗi khi tôi yêu cầu những gì tôi nghĩ là mới nonce, tôi nhận được nonce trở lại từ Wordpress. Tôi hiểu khái niệm về nonce_life của WP và thậm chí đặt nó thành một thứ khác, nhưng điều đó đã không giúp tôi.

Tôi không tạo ra nonce trong đối tượng JS trong tiêu đề thông qua nội địa hóa - Tôi làm điều đó trên trang hiển thị của mình. Tôi có thể lấy trang của mình để xử lý yêu cầu Ajax, nhưng khi tôi yêu cầu một nonce mới từ WP trong cuộc gọi lại, tôi nhận lại cùng một lần nữa và tôi không biết mình đang làm gì sai ... Cuối cùng tôi muốn mở rộng điều này để có thể có nhiều mục trên trang, mỗi mục có khả năng thêm / xóa-- vì vậy tôi cần một giải pháp cho phép nhiều yêu cầu Ajax tiếp theo từ một trang.

(Và tôi nên nói rằng tôi đã đưa tất cả các chức năng này vào một plugin, vì vậy "trang hiển thị" mặt trước thực sự là một chức năng đi kèm với plugin ...)

Hàm.php: bản địa hóa, nhưng tôi không tạo một nonce ở đây

wp_localize_script('myjs', 'ajaxVars', array('ajaxurl' => 'admin-ajax.php')));

Gọi cho JS:

$("#myelement").click(function(e) {
    e.preventDefault();
    post_id = $(this).data("data-post-id");
    user_id = $(this).data("data-user-id");
    nonce = $(this).data("data-nonce");
    $.ajax({
      type: "POST",
      dataType: "json",
      url: ajaxVars.ajaxurl,
      data: {
         action: "myfaves",
         post_id: post_id,
         user_id: user_id,
         nonce: nonce
      },
      success: function(response) {
         if(response.type == "success") {
            nonce = response.newNonce;
            ... other stuff
         }
      }
  });
});

Nhận PHP:

function myFaves() {
   $ajaxNonce = 'myplugin_myaction_nonce_' . $postID;
   if (!wp_verify_nonce($_POST['nonce'], $ajaxNonce))
      exit('Sorry!');

   // Get various POST vars and do some other stuff...

   // Prep JSON response & generate new, unique nonce
   $newNonce = wp_create_nonce('myplugin_myaction_nonce_' . $postID . '_' 
       . str_replace('.', '', gettimeofday(true)));
   $response['newNonce'] = $newNonce;

   // Also let the page process itself if there is no JS/Ajax capability
   } else {
      header("Location: " . $_SERVER["HTTP_REFERER"];
   }
   die();
}

Chức năng hiển thị Frontend PHP, trong số đó là:

$nonce = wp_create_nonce('myplugin_myaction_nonce_' . $post->ID);
$link = admin_url('admin-ajax.php?action=myfaves&post_id=' . $post->ID
   . '&user_id=' . $user_ID
   . '&nonce=' . $nonce);

echo '<a id="myelement" data-post-id="' . $post->ID
   . '" data-user-id="' . $user_ID
   . '" data-nonce="' . $nonce
   . '" href="' . $link . '">My Link</a>';

Tại thời điểm này, tôi thực sự biết ơn về bất kỳ manh mối hay gợi ý nào trong việc giúp WP tạo lại một số lần duy nhất cho mỗi yêu cầu Ajax mới ...


CẬP NHẬT: Tôi đã giải quyết vấn đề của mình. Các đoạn mã ở trên là hợp lệ, tuy nhiên tôi đã thay đổi cách tạo $ newNonce trong cuộc gọi lại PHP để nối thêm chuỗi micro giây để đảm bảo rằng nó là duy nhất cho các yêu cầu Ajax tiếp theo.


Từ một cái nhìn rất ngắn gọn: Bạn đang tạo ra nonce sau khi bạn nhận được nó (trên màn hình)? Tại sao bạn không tạo nó trong cuộc gọi nội địa hóa?
kaiser

JQuery đang sử dụng nonce ban đầu từ thuộc tính "data-nonce" trong liên kết # myelement và ý tưởng là trang có thể được xử lý bởi Ajax hoặc chính nó. Dường như với tôi rằng việc tạo nonce một lần thông qua cuộc gọi nội địa hóa sẽ loại trừ nó khỏi quá trình xử lý không phải là JS, nhưng tôi có thể sai về điều đó. Dù bằng cách nào, Wordpress cũng cho tôi trở lại cùng một lần nữa ...
Tim

Ngoài ra: Việc không đặt nonce trong cuộc gọi nội địa hóa sẽ ngăn một người có nhiều mục trên một trang trong đó mỗi mục có thể có một lần duy nhất cho một yêu cầu Ajax?
Tim

Tạo nonce bên trong bản địa hóa sẽ tạo và làm cho nó có sẵn cho một tập lệnh đó. Nhưng bạn cũng có thể thêm một số lượng không giới hạn các giá trị nội địa hóa khác (tên được đặt tên) với các giá trị riêng biệt.
kaiser

Nếu bạn đã giải quyết xong, bạn nên đăng câu trả lời của mình và đánh dấu là "được chấp nhận". Nó sẽ giúp giữ cho trang web được tổ chức. Tôi chỉ làm rối mã của bạn và một vài điều không hiệu quả với tôi, vì vậy hãy tăng gấp đôi yêu cầu đó để bạn đăng giải pháp của mình.
s_ha_dum

Câu trả lời:


6

Đây là một câu trả lời rất dài cho câu hỏi của riêng tôi, vượt ra ngoài việc giải quyết câu hỏi về việc tạo ra các lực lượng duy nhất cho các yêu cầu Ajax tiếp theo. Đây là tính năng "thêm vào mục yêu thích" được tạo ra chung cho mục đích trả lời (tính năng của tôi cho phép người dùng thêm ID bài đăng của tệp đính kèm ảnh vào danh sách yêu thích, nhưng điều này có thể áp dụng cho nhiều tính năng khác dựa vào Ajax). Tôi đã mã hóa đây là một plugin độc lập và có một vài mục bị thiếu - nhưng điều này cần đủ chi tiết để cung cấp ý chính nếu bạn muốn sao chép tính năng này. Nó sẽ hoạt động trên một bài đăng / trang riêng lẻ, nhưng nó cũng sẽ hoạt động trong danh sách các bài đăng (ví dụ: bạn có thể thêm / xóa các mục vào nội dung yêu thích thông qua Ajax và mỗi bài đăng sẽ có một thông báo riêng cho từng yêu cầu Ajax). Hãy nhớ rằng có '

script.php

/**
* Enqueue front-end jQuery
*/
function enqueueFavoritesJS()
{
    // Only show Favorites Ajax JS if user is logged in
    if (is_user_logged_in()) {
        wp_enqueue_script('favorites-js', MYPLUGIN_BASE_URL . 'js/favorites.js', array('jquery'));
        wp_localize_script('favorites-js', 'ajaxVars', array('ajaxurl' => admin_url('admin-ajax.php')));
    }
}
add_action('wp_enqueue_scripts', 'enqueueFavoritesJS');

Favorites.js (Rất nhiều công cụ gỡ lỗi có thể được gỡ bỏ)

$(document).ready(function()
{
    // Toggle item in Favorites
    $(".faves-link").click(function(e) {
        // Prevent self eval of requests and use Ajax instead
        e.preventDefault();
        var $this = $(this);
        console.log("Starting click event...");

        // Fetch initial variables from the page
        post_id = $this.attr("data-post-id");
        user_id = $this.attr("data-user-id");
        the_toggle = $this.attr("data-toggle");
        ajax_nonce = $this.attr("data-nonce");

        console.log("data-post-id: " + post_id);
        console.log("data-user-id: " + user_id);
        console.log("data-toggle: " + the_toggle);
        console.log("data-nonce: " + ajax_nonce);
        console.log("Starting Ajax...");

        $.ajax({
            type: "POST",
            dataType: "json",
            url: ajaxVars.ajaxurl,
            data: {
                // Send JSON back to PHP for eval
                action : "myFavorites",
                post_id: post_id,
                user_id: user_id,
                _ajax_nonce: ajax_nonce,
                the_toggle: the_toggle
            },
            beforeSend: function() {
                if (the_toggle == "y") {
                    $this.text("Removing from Favorites...");
                    console.log("Removing...");
                } else {
                    $this.text("Adding to Favorites...");
                    console.log("Adding...");
                }
            },
            success: function(response) {
                // Process JSON sent from PHP
                if(response.type == "success") {
                    console.log("Success!");
                    console.log("New nonce: " + response.newNonce);
                    console.log("New toggle: " + response.theToggle);
                    console.log("Message from PHP: " + response.message);
                    $this.text(response.message);
                    $this.attr("data-toggle", response.theToggle);
                    // Set new nonce
                    _ajax_nonce = response.newNonce;
                    console.log("_ajax_nonce is now: " + _ajax_nonce);
                } else {
                    console.log("Failed!");
                    console.log("New nonce: " + response.newNonce);
                    console.log("Message from PHP: " + response.message);
                    $this.parent().html("<p>" + response.message + "</p>");
                    _ajax_nonce = response.newNonce;
                    console.log("_ajax_nonce is now: " + _ajax_nonce);
                }
            },
            error: function(e, x, settings, exception) {
                // Generic debugging
                var errorMessage;
                var statusErrorMap = {
                    '400' : "Server understood request but request content was invalid.",
                    '401' : "Unauthorized access.",
                    '403' : "Forbidden resource can't be accessed.",
                    '500' : "Internal Server Error",
                    '503' : "Service Unavailable"
                };
                if (x.status) {
                    errorMessage = statusErrorMap[x.status];
                    if (!errorMessage) {
                        errorMessage = "Unknown Error.";
                    } else if (exception == 'parsererror') {
                        errorMessage = "Error. Parsing JSON request failed.";
                    } else if (exception == 'timeout') {
                        errorMessage = "Request timed out.";
                    } else if (exception == 'abort') {
                        errorMessage = "Request was aborted by server.";
                    } else {
                        errorMessage = "Unknown Error.";
                    }
                    $this.parent().html(errorMessage);
                    console.log("Error message is: " + errorMessage);
                } else {
                    console.log("ERROR!!");
                    console.log(e);
                }
            }
        }); // Close $.ajax
    }); // End click event
});

Các chức năng (hiển thị mặt trước & hành động Ajax)

Để xuất liên kết Thêm / Xóa Mục ưa thích, chỉ cần gọi nó trên trang / bài đăng của bạn thông qua:

if (function_exists('myFavoritesLink') {
    myFavoritesLink($user_ID, $post->ID);
}

Chức năng hiển thị mặt trước:

function myFavoritesLink($user_ID, $postID)
{
    global $user_ID;
    if (is_user_logged_in()) {
        // Set initial element toggle value & link text - udpated by callback
        $myUserMeta = get_user_meta($user_ID, 'myMetadata', true);
        if (is_array($myUserMeta['metadata']) && in_array($postID, $myUserMeta['metadata'])) {
            $toggle = "y";
            $linkText = "Remove from Favorites";
        } else {
            $toggle = "n";
            $linkText = "Add to Favorites";
        }

        // Create Ajax-only nonce for initial request only
        // New nonce returned in callback
        $ajaxNonce = wp_create_nonce('myplugin_myaction_' . $postID);
        echo '<p class="faves-action"><a class="faves-link"' 
            . ' data-post-id="' . $postID 
            . '" data-user-id="' . $user_ID  
            . '" data-toggle="' . $toggle 
            . '" data-nonce="' . $ajaxNonce 
            . '" href="#">' . $linkText . '</a></p>' . "\n";

    } else {
        // User not logged in
        echo '<p>Sign in to use the Favorites feature.</p>' . "\n";
    }

}

Hàm hành động Ajax:

/**
* Toggle add/remove for Favorites
*/
function toggleFavorites()
{
    if (is_user_logged_in()) {
        // Verify nonce
        $ajaxNonce = 'myplugin_myaction' . $_POST['post_id'];
        if (! wp_verify_nonce($_POST['_ajax_nonce'], $ajaxNonce)) {
            exit('Sorry!');
        }
        // Process POST vars
        if (isset($_POST['post_id']) && is_numeric($_POST['post_id'])) {
            $postID = $_POST['post_id'];
        } else {
            return;
        }
        if (isset($_POST['user_id']) && is_numeric($_POST['user_id'])) {
            $userID = $_POST['user_id'];
        } else {
            return;
        }
        if (isset($_POST['the_toggle']) && ($_POST['the_toggle'] === "y" || $_POST['the_toggle'] === "n")) {
            $toggle = $_POST['the_toggle'];
        } else {
            return;
        }

        $myUserMeta = get_user_meta($userID, 'myMetadata', true);

        // Init myUserMeta array if it doesn't exist
        if ($myUserMeta['myMetadata'] === '' || ! is_array($myUserMeta['myMetadata'])) {
            $myUserMeta['myMetadata'] = array();
        }

        // Toggle the item in the Favorites list
        if ($toggle === "y" && in_array($postID, $myUserMeta['myMetadata'])) {
            // Remove item from Favorites list
            $myUserMeta['myMetadata'] = array_flip($myUserMeta['myMetadata']);
            unset($myUserMeta['myMetadata'][$postID]);
            $myUserMeta['myMetadata'] = array_flip($myUserMeta['myMetadata']);
            $myUserMeta['myMetadata'] = array_values($myUserMeta['myMetadata']);
            $newToggle = "n";
            $message = "Add to Favorites";
        } else {
            // Add item to Favorites list
            $myUserMeta['myMetadata'][] = $postID;
            $newToggle = "y";
            $message = "Remove from Favorites";
        }

        // Prep for the response
        // Nonce for next request - unique with microtime string appended
        $newNonce = wp_create_nonce('myplugin_myaction_' . $postID . '_' 
            . str_replace('.', '', gettimeofday(true)));
        $updateUserMeta = update_user_meta($userID, 'myMetadata', $myUserMeta);

        // Response to jQuery
        if($updateUserMeta === false) {
            $response['type'] = "error";
            $response['theToggle'] = $toggle;
            $response['message'] = "Your Favorites could not be updated.";
            $response['newNonce'] = $newNonce;
        } else {
            $response['type'] = "success";
            $response['theToggle'] = $newToggle;
            $response['message'] = $message;
            $response['newNonce'] = $newNonce;
        }

        // Process with Ajax, otherwise process with self
        if (! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && 
            strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
                $response = json_encode($response);
                echo $response;
        } else {
            header("Location: " . $_SERVER["HTTP_REFERER"]);
        }
        exit();
    } // End is_user_logged_in()
}
add_action('wp_ajax_myFavorites', 'toggleFavorites');

3

Tôi thực sự phải đặt câu hỏi về lý do đằng sau việc có một nonce mới cho mỗi yêu cầu ajax. Bản gốc nonce sẽ hết hạn, nhưng nó có thể được sử dụng nhiều lần cho đến khi nó được sử dụng. Có javascript nhận nó thông qua ajax đánh bại mục đích, đặc biệt là cung cấp nó trong trường hợp lỗi. (Mục đích của nonces là một chút bảo mật để liên kết một hành động với người dùng trong khung thời gian.)

Tôi không được đề cập đến các câu trả lời khác, nhưng tôi mới và không thể nhận xét ở trên, vì vậy liên quan đến "giải pháp" đã đăng, bạn sẽ nhận được một thông báo mới mỗi lần nhưng không sử dụng nó trong yêu cầu. Chắc chắn sẽ rất khó khăn để có được micro giây giống nhau mỗi lần để khớp với từng nonce mới được tạo theo cách đó. Mã PHP đang xác minh đối với nonce ban đầu và javascript đang cung cấp nonce gốc ... vì vậy nó hoạt động (vì nó chưa hết hạn).


1
Vấn đề là, nonce hết hạn sau khi được sử dụng và sẽ trả về -1 trong hàm ajax sau mỗi lần. Đây là vấn đề nếu bạn đang xác thực các phần của biểu mẫu trong PHP và trả về lỗi để in ra. Biểu mẫu nonce đã được sử dụng, nhưng thực sự đã xảy ra lỗi trong xác thực php của các trường và khi biểu mẫu được gửi lại, lần này, nó không thể được xác minh và check_ajax_referertrả về -1, đó không phải là điều chúng tôi muốn!
Solomon Closson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.