it-swarm.com.de

Wie erhält man eine eindeutige Nonce für jede Ajax-Anfrage?

Ich habe ein paar Diskussionen darüber gesehen, wie man Wordpress dazu bringt, eine einmalige Nonce für nachfolgende Ajax-Anfragen zu regenerieren, aber für mein ganzes Leben kann ich Wordpress nicht dazu bringen - jedes Mal, wenn ich eine neue Anfrage mache, was ich denke nonce, ich bekomme das gleiche nonce von Wordpress zurück. Ich verstehe das Konzept von WP's nonce_life und setze es sogar auf etwas anderes, aber das hat mir nicht geholfen.

Das Nonce im JS-Objekt in der Kopfzeile wird nicht über die Lokalisierung generiert, sondern auf meiner Anzeigeseite. Ich kann meine Seite veranlassen, die Ajax-Anfrage zu verarbeiten, aber wenn ich eine neue Nonce von WP im Rückruf anfordere, erhalte ich dieselbe Nonce zurück und weiß nicht, was ich falsch mache. Letztendlich möchte ich dies so erweitern, dass es mehrere Elemente auf der Seite geben kann, die jeweils hinzugefügt/entfernt werden können. Daher benötige ich eine Lösung, die mehrere nachfolgende Ajax-Anforderungen von einer Seite zulässt.

(Und ich sollte sagen, dass ich all diese Funktionen in ein Plugin integriert habe, sodass die Front-End- "Anzeigeseite" eigentlich eine Funktion ist, die im Plugin enthalten ist ...)

functions.php: lokalisiere, aber ich erstelle hier keine Nonce

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

JS anrufen:

$("#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
         }
      }
  });
});

Empfangen von 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();
}

Frontend PHP Anzeigefunktion, darunter:

$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>';

Zu diesem Zeitpunkt wäre ich wirklich dankbar für alle Hinweise oder Hinweise, um WP zu erhalten, um für jede neue Ajax-Anfrage eine eindeutige Nonce zu generieren ...


UPDATE: Ich habe mein Problem gelöst. Die obigen Codefragmente sind gültig, jedoch habe ich die $ newNonce-Erstellung im Callback von PHP geändert, um eine Mikrosekundenzeichenfolge anzufügen, um sicherzustellen, dass sie bei nachfolgenden Ajax-Anforderungen eindeutig ist.

10
Tim

Hier ist eine sehr ausführliche Antwort auf meine eigene Frage, die über die Frage der Generierung eindeutiger Nonces für nachfolgende Ajax-Anforderungen hinausgeht. Dies ist eine "Zu Favoriten hinzufügen" -Funktion, die für die Zwecke der Antwort generisch gemacht wurde (mit meiner Funktion können Benutzer die Beitrags-IDs von Fotoanhängen zu einer Liste von Favoriten hinzufügen, dies kann jedoch für eine Vielzahl anderer Funktionen gelten, die darauf angewiesen sind Ajax). Ich habe dieses Plugin als eigenständiges Plugin codiert, und es fehlen einige Elemente. Dies sollte jedoch detailliert genug sein, um das Gist bereitzustellen, wenn Sie die Funktion replizieren möchten. Es funktioniert für einzelne Posts/Seiten, aber auch für Listen von Posts (z. B. können Sie Elemente zu Favoriten inline über Ajax hinzufügen/entfernen, und jeder Post hat für jede Ajax-Anfrage eine eigene Nonce). Denken Sie daran, dass es wahrscheinlich eine effizientere und/oder elegantere Möglichkeit gibt. Derzeit funktioniert dies nur für Ajax. Ich habe mich noch nicht darum gekümmert, Nicht-Ajax-$ _POST-Daten zu verarbeiten.

scripts.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');

favourites.js (Viele Debug-Sachen, die entfernt werden können)

$(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
});

Funktionen (Frontend-Display & Ajax-Aktion)

Um den Link Favoriten hinzufügen/entfernen auszugeben, rufen Sie ihn einfach auf Ihrer Seite/in Ihrem Post auf über:

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

Frontend-Anzeigefunktion:

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";
    }

}

Ajax-Aktionsfunktion:

/**
* 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');
5
Tim

Ich muss wirklich die Argumentation hinterfragen, die dahinter steckt, für jede Ajax-Anfrage eine neue Nonce zu bekommen. Die ursprüngliche Nonce läuft ab, kann jedoch mehrmals verwendet werden, bis dies der Fall ist. Wenn Sie das Javascript über Ajax empfangen, wird dies dem Zweck nicht gerecht, insbesondere, wenn Sie es in einem Fehlerfall bereitstellen. (Der Zweck von Nonces ist ein wenig Sicherheit für die Zuordnung einer Aktion zu einem Benutzer innerhalb eines Zeitrahmens.)

Ich sollte keine anderen Antworten erwähnen, aber ich bin neu und kann oben keinen Kommentar abgeben. In Bezug auf die gepostete "Lösung" erhalten Sie jedes Mal eine neue Nonce, verwenden sie jedoch nicht in der Anfrage. Es wäre sicherlich schwierig, die Mikrosekunden jedes Mal gleich hoch zu halten, um mit jeder neuen Nonce übereinzustimmen, die auf diese Weise erstellt wurde. Der PHP Code überprüft die ursprüngliche Nonce und das Javascript liefert die ursprüngliche Nonce ... so funktioniert es (weil es noch nicht abgelaufen ist).

3
Joy Reynolds