Wie erhalte ich für jede Ajax-Anfrage eine eindeutige Nonce?

10

Ich habe einige Diskussionen darüber gesehen, wie Wordpress eine eindeutige Nonce für nachfolgende Ajax-Anfragen neu generieren kann, aber für mein Leben kann ich Wordpress nicht dazu bringen, dies zu tun - jedes Mal, wenn ich anfordere, was meiner Meinung nach neu sein sollte 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.

Ich generiere das Nonce im JS-Objekt im Header nicht über die Lokalisierung - ich mache es auf meiner Anzeigeseite. Ich kann meine Seite dazu bringen, die Ajax-Anfrage zu verarbeiten, aber wenn ich im Rückruf eine neue Nonce von WP anfordere, bekomme ich dieselbe Nonce zurück und weiß nicht, was ich falsch mache ... Letztendlich möchte ich Erweitern Sie dies so, dass sich auf der Seite mehrere Elemente befinden können, die jeweils hinzugefügt / entfernt werden können. Daher benötige ich eine Lösung, die mehrere nachfolgende Ajax-Anforderungen von einer Seite aus zulässt.

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

functions.php: lokalisieren, 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
         }
      }
  });
});

PHP empfangen:

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

An dieser Stelle wäre ich wirklich dankbar für irgendwelche Anhaltspunkte oder Hinweise WP in immer einen einzigartigen nonce für jede neue Ajax - Anfrage zu regenerieren ...


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

Tim
quelle
Von einem sehr kurzen Blick: Sie erstellen das Nonce, nachdem Sie es erhalten haben (auf dem Display)? Warum erstellen Sie es nicht während des Lokalisierungsaufrufs?
Kaiser
Die jQuery verwendet die anfängliche Nonce aus dem Attribut "data-nonce" im Link a # myelement. Die Idee ist, dass die Seite entweder von Ajax oder von sich selbst verarbeitet werden kann. Es schien mir, dass das einmalige Erstellen der Nonce über den Localize-Aufruf sie von der Nicht-JS-Verarbeitung ausschließen würde, aber ich könnte mich darin irren. So oder so gibt mir Wordpress die gleiche Nonce zurück ...
Tim
Außerdem: Würde das Einfügen der Nonce in den Lokalisierungsaufruf nicht verhindern, dass eines mehrere Elemente auf einer Seite enthält, auf der jedes Element eine eindeutige Nonce für eine Ajax-Anforderung haben könnte?
Tim
Wenn Sie die Nonce innerhalb der Lokalisierung erstellen, wird sie für dieses eine Skript erstellt und verfügbar gemacht. Sie können aber auch eine unbegrenzte Anzahl anderer (mit Schlüssel benannter) Lokalisierungswerte mit separaten Nonces hinzufügen.
Kaiser
Wenn Sie es gelöst haben, werden Sie aufgefordert, Ihre Antwort zu posten und als "akzeptiert" zu markieren. Dies wird dazu beitragen, die Website organisiert zu halten. Ich habe nur mit Ihrem Code herumgespielt und ein paar Dinge funktionieren bei mir nicht. Verdoppeln Sie also die Aufforderung, Ihre Lösung zu veröffentlichen.
s_ha_dum

Antworten:

5

Hier ist eine sehr ausführliche Antwort auf meine eigene Frage, die über die Frage hinausgeht, eindeutige Nonces für nachfolgende Ajax-Anfragen zu generieren. Dies ist eine Funktion zum Hinzufügen zu Favoriten, die für die Zwecke der Antwort allgemein festgelegt wurde (mit meiner Funktion können Benutzer die Post-IDs von Fotoanhängen zu einer Liste von Favoriten hinzufügen, dies kann jedoch auch für eine Vielzahl anderer Funktionen gelten, auf die sich diese stützen Ajax). Ich habe dies als eigenständiges Plugin codiert, und es fehlen einige Elemente - aber dies sollte ausreichend detailliert sein, um das Wesentliche zu liefern, wenn Sie die Funktion replizieren möchten. Es funktioniert für einen einzelnen Beitrag / eine einzelne Seite, aber auch für Listen von Beiträgen (z. B. können Sie über Ajax Elemente zu Favoriten inline hinzufügen / daraus entfernen, und jeder Beitrag hat für jede Ajax-Anfrage eine eigene eindeutige Nonce). Denken Sie daran, dass dort '

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

Favoriten.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-Anzeige & Ajax-Aktion)

Um den Link Favoriten hinzufügen / entfernen auszugeben, rufen Sie ihn einfach auf Ihrer Seite / Ihrem Beitrag 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');
Tim
quelle
3

Ich muss wirklich die Gründe hinterfragen, die dahinter stehen, 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 das Javascript es über Ajax empfängt, wird der Zweck zunichte gemacht, insbesondere wenn es in einem Fehlerfall bereitgestellt wird. (Der Zweck von Nonces ist eine kleine 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 veröffentlichte "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 zu machen, um mit jeder neuen Nonce übereinzustimmen, die auf diese Weise erstellt wurde. Der PHP-Code wird gegen das ursprüngliche Nonce überprüft, und das Javascript liefert das ursprüngliche Nonce ... also funktioniert es (weil es noch nicht abgelaufen ist).

Joy Reynolds
quelle
1
Das Problem ist, dass nonce nach seiner Verwendung abläuft und nach jedem Mal -1 in der Ajax-Funktion zurückgibt. Dies ist ein Problem, wenn Sie Teile eines Formulars in PHP validieren und Fehler zum Ausdrucken zurückgeben. Das Formular nonce wurde verwendet, aber bei der PHP-Validierung der Felder ist tatsächlich ein Fehler aufgetreten. Wenn das Formular erneut gesendet wird, kann es diesmal nicht überprüft werden und check_ajax_referergibt -1 zurück, was nicht das ist, was wir wollen!
Solomon Closson