Cum să obții un nonce unic pentru fiecare cerere Ajax?

29 ian. 2013, 04:59:40
Vizualizări: 17.6K
Voturi: 12

Am văzut câteva discuții despre generarea unui nonce unic pentru cererile Ajax ulterioare în WordPress, dar oricât aș încerca, nu reușesc să fac WordPress să îl regenereze – de fiecare dată când solicit un nonce nou, primesc același nonce înapoi. Înțeleg conceptul nonce_life din WordPress și chiar l-am setat la o valoare diferită, dar asta nu m-a ajutat.

Nu generez nonce-ul în obiectul JS din header prin localizare – o fac pe pagina de afișare. Pot procesa cererea Ajax, dar când solicit un nou nonce în callback, primesc același nonce și nu știu ce fac greșit... În final, vreau să extind asta pentru a putea avea mai multe elemente pe pagină, fiecare cu capacitatea de a adăuga/elimina – deci am nevoie de o soluție care să permită multiple cereri Ajax ulterioare de pe o singură pagină.

(Și ar trebui să menționez că am pus toată această funcționalitate într-un plugin, deci "pagina de afișare" este de fapt o funcție inclusă în plugin...)

functions.php: localizez, dar nu creez un nonce aici

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

JavaScript care face apelul:

$("#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;
            ... alte acțiuni
         }
      }
  });
});

PHP care primește cererea:

function myFaves() {
   $ajaxNonce = 'myplugin_myaction_nonce_' . $postID;
   if (!wp_verify_nonce($_POST['nonce'], $ajaxNonce))
      exit('Ne pare rău!');

   // Prelucrează variabilele POST și alte acțiuni...

   // Pregătește răspunsul JSON și generează un nou nonce unic
   $newNonce = wp_create_nonce('myplugin_myaction_nonce_' . $postID . '_' 
       . str_replace('.', '', gettimeofday(true)));
   $response['newNonce'] = $newNonce;

   // Permite procesarea paginii și fără JS/Ajax
   } else {
      header("Location: " . $_SERVER["HTTP_REFERER"]);
   }
   die();
}

Funcția PHP de afișare din frontend, care include:

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

În acest moment, aș fi foarte recunoscător pentru orice indicii sau sugestii pentru a face WordPress să regenereze un nonce unic pentru fiecare nouă cerere Ajax...


ACTUALIZARE: Am rezolvat problema. Fragmentele de cod de mai sus sunt valide, dar am modificat crearea $newNonce în callback-ul PHP pentru a adăuga un șir de microsecunde, asigurându-mă că este unic pentru cererile Ajax ulterioare.

5
Comentarii

După o privire foarte scurtă: Creezi nonce-ul după ce l-ai primit (la afișare)? De ce nu îl creezi în timpul apelului localize?

kaiser kaiser
29 ian. 2013 07:36:36

jQuery folosește nonce-ul inițial din atributul "data-nonce" din linkul a#myelement, iar ideea este că pagina poate fi procesată fie prin Ajax, fie de sine stătător. Mi s-a părut că crearea nonce-ului o singură dată prin apelul localize l-ar exclude din procesarea non-JS, dar s-ar putea să greșesc în privința asta. Oricum, Wordpress îmi returnează același nonce...

Tim Tim
29 ian. 2013 17:17:11

De asemenea: Nu ar însemna că plasarea nonce-ului în apelul localize ar împiedica posibilitatea de a avea mai multe elemente pe o pagină, fiecare cu propriul nonce unic pentru o cerere Ajax?

Tim Tim
29 ian. 2013 18:11:09

Crearea nonce-ului în interiorul localizării l-ar crea și l-ar face disponibil doar pentru acel script. Dar ai putea adăuga și oricâte alte valori localizate (cu nume de chei) cu nonce-uri separate.

kaiser kaiser
29 ian. 2013 21:32:50

Dacă ai rezolvat problema, este recomandat să postezi răspunsul tău și să-l marchezi drept "acceptat". Acest lucru va ajuta la menținerea organizării site-ului. Tocmai am experimentat cu codul tău și câteva lucruri nu funcționează pentru mine, așa că reiter cererea de a posta soluția ta.

s_ha_dum s_ha_dum
1 feb. 2013 17:55:30
Toate răspunsurile la întrebare 2
0

Iată un răspuns foarte detaliat la propria mea întrebare, care depășește simpla abordare a generării de nonce-uri unice pentru cererile Ajax ulterioare. Aceasta este o funcționalitate de "adaugă la favorite" care a fost făcută generică în scopul răspunsului (funcționalitatea mea permite utilizatorilor să adauge ID-urile postărilor de atașamente foto într-o listă de favorite, dar aceasta ar putea fi aplicată și altor funcționalități care se bazează pe Ajax). Am codat acest lucru ca un plugin independent și lipsesc câteva elemente, dar acest lucru ar trebui să ofere suficiente detalii pentru a înțelege esența dacă doriți să replicați funcționalitatea. Va funcționa pe o postare/pagină individuală, dar va funcționa și în liste de postări (de exemplu, puteți adăuga/elimina elemente din favorite inline prin Ajax și fiecare postare va avea propriul nonce unic pentru fiecare cerere Ajax). Rețineți că există probabil o metodă mai eficientă și/sau mai elegantă de a face acest lucru, iar în prezent acest lucru funcționează doar pentru Ajax - nu m-am deranjat încă să procesez datele $_POST non-Ajax.

scripts.php

/**
* Încarcă jQuery pentru front-end
*/
function enqueueFavoritesJS()
{
    // Afișează JS-ul pentru Ajax Favorites doar dacă utilizatorul este autentificat
    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 (Multe elemente de debug care pot fi eliminate)

$(document).ready(function()
{
    // Comută elementul în Favorite
    $(".faves-link").click(function(e) {
        // Previne evaluarea automată a cererilor și folosește Ajax în schimb
        e.preventDefault();
        var $this = $(this);
        console.log("Începe evenimentul de click...");

        // Preluare variabile inițiale de pe pagină
        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("Începe Ajax...");

        $.ajax({
            type: "POST",
            dataType: "json",
            url: ajaxVars.ajaxurl,
            data: {
                // Trimite JSON înapoi la PHP pentru evaluare
                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("Se elimină din Favorite...");
                    console.log("Se elimină...");
                } else {
                    $this.text("Se adaugă în Favorite...");
                    console.log("Se adaugă...");
                }
            },
            success: function(response) {
                // Procesează JSON trimis de la PHP
                if(response.type == "success") {
                    console.log("Succes!");
                    console.log("Nonce nou: " + response.newNonce);
                    console.log("Comutare nouă: " + response.theToggle);
                    console.log("Mesaj de la PHP: " + response.message);
                    $this.text(response.message);
                    $this.attr("data-toggle", response.theToggle);
                    // Setează nonce nou
                    _ajax_nonce = response.newNonce;
                    console.log("_ajax_nonce este acum: " + _ajax_nonce);
                } else {
                    console.log("Eșuat!");
                    console.log("Nonce nou: " + response.newNonce);
                    console.log("Mesaj de la PHP: " + response.message);
                    $this.parent().html("<p>" + response.message + "</p>");
                    _ajax_nonce = response.newNonce;
                    console.log("_ajax_nonce este acum: " + _ajax_nonce);
                }
            },
            error: function(e, x, settings, exception) {
                // Debug generic
                var errorMessage;
                var statusErrorMap = {
                    '400' : "Serverul a înțeles cererea, dar conținutul acesteia a fost invalid.",
                    '401' : "Acces neautorizat.",
                    '403' : "Resursa interzisă nu poate fi accesată.",
                    '500' : "Eroare Internă de Server",
                    '503' : "Serviciu Indisponibil"
                };
                if (x.status) {
                    errorMessage = statusErrorMap[x.status];
                    if (!errorMessage) {
                        errorMessage = "Eroare necunoscută.";
                    } else if (exception == 'parsererror') {
                        errorMessage = "Eroare. Analiza cererii JSON a eșuat.";
                    } else if (exception == 'timeout') {
                        errorMessage = "Cererea a expirat.";
                    } else if (exception == 'abort') {
                        errorMessage = "Cererea a fost întreruptă de server.";
                    } else {
                        errorMessage = "Eroare necunoscută.";
                    }
                    $this.parent().html(errorMessage);
                    console.log("Mesajul de eroare este: " + errorMessage);
                } else {
                    console.log("EROARE!!");
                    console.log(e);
                }
            }
        }); // Închide $.ajax
    }); // Încheie evenimentul de click
});

Funcții (afișare front-end & acțiune Ajax)

Pentru a afișa linkul de Adăugare/Eliminare Favorite, apelați-l pur și simplu pe pagina/postarea dvs. prin:

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

Funcția de afișare front-end:

function myFavoritesLink($user_ID, $postID)
{
    global $user_ID;
    if (is_user_logged_in()) {
        // Setează valoarea inițială de comutare a elementului și textul linkului - actualizat de callback
        $myUserMeta = get_user_meta($user_ID, 'myMetadata', true);
        if (is_array($myUserMeta['metadata']) && in_array($postID, $myUserMeta['metadata'])) {
            $toggle = "y";
            $linkText = "Elimină din Favorite";
        } else {
            $toggle = "n";
            $linkText = "Adaugă la Favorite";
        }

        // Creează nonce doar pentru cererea Ajax inițială
        // Nonce nou returnat în 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 {
        // Utilizator neautentificat
        echo '<p>Autentifică-te pentru a folosi funcționalitatea Favorite.</p>' . "\n";
    }

}

Funcția de acțiune Ajax:

/**
* Comută adăugarea/eliminarea pentru Favorite
*/
function toggleFavorites()
{
    if (is_user_logged_in()) {
        // Verifică nonce
        $ajaxNonce = 'myplugin_myaction' . $_POST['post_id'];
        if (! wp_verify_nonce($_POST['_ajax_nonce'], $ajaxNonce)) {
            exit('Ne pare rău!');
        }
        // Procesează variabilele POST
        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);

        // Inițializează array-ul myUserMeta dacă nu există
        if ($myUserMeta['myMetadata'] === '' || ! is_array($myUserMeta['myMetadata'])) {
            $myUserMeta['myMetadata'] = array();
        }

        // Comută elementul în lista de Favorite
        if ($toggle === "y" && in_array($postID, $myUserMeta['myMetadata'])) {
            // Elimină elementul din lista de Favorite
            $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 = "Adaugă la Favorite";
        } else {
            // Adaugă elementul în lista de Favorite
            $myUserMeta['myMetadata'][] = $postID;
            $newToggle = "y";
            $message = "Elimină din Favorite";
        }

        // Pregătește pentru răspuns
        // Nonce pentru următoarea cerere - unic cu șir microtime atașat
        $newNonce = wp_create_nonce('myplugin_myaction_' . $postID . '_' 
            . str_replace('.', '', gettimeofday(true)));
        $updateUserMeta = update_user_meta($userID, 'myMetadata', $myUserMeta);

        // Răspuns către jQuery
        if($updateUserMeta === false) {
            $response['type'] = "error";
            $response['theToggle'] = $toggle;
            $response['message'] = "Lista ta de Favorite nu a putut fi actualizată.";
            $response['newNonce'] = $newNonce;
        } else {
            $response['type'] = "success";
            $response['theToggle'] = $newToggle;
            $response['message'] = $message;
            $response['newNonce'] = $newNonce;
        }

        // Procesează cu Ajax, altfel procesează automat
        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();
    } // Încheie is_user_logged_in()
}
add_action('wp_ajax_myFavorites', 'toggleFavorites');
1 feb. 2013 19:47:34
1

Chiar trebuie să pun la îndoială raționamentul din spatele obținerii unui nou nonce pentru fiecare cerere AJAX. Nonce-ul original va expira, dar poate fi folosit de mai multe ori până când se întâmplă acest lucru. Primirea nonce-ului prin JavaScript prin intermediul AJAX îi subminează scopul, mai ales furnizându-l în caz de eroare. (Scopul nonce-urilor este de a oferi o anumită securitate prin asocierea unei acțiuni cu un utilizator într-un interval de timp.)

Nu ar trebui să menționez alte răspunsuri, dar sunt nou și nu pot comenta mai sus, așa că în legătură cu "soluția" postată, obții un nou nonce de fiecare dată, dar nu îl folosești în cerere. Cu siguranță ar fi complicat să obții aceleași microsecunde de fiecare dată pentru a potrivi fiecare nou nonce creat în acest fel. Codul PHP verifică împotriva nonce-ului original, iar JavaScript furnizează nonce-ul original... așa că funcționează (pentru că nu a expirat încă).

28 nov. 2013 06:15:41
Comentarii

Problema este că nonce-ul expiră după ce este folosit și va returna -1 în funcția ajax după fiecare utilizare. Aceasta este o problemă dacă validezi părți ale unui formular în PHP și returnezi erori pentru afișare. Nonce-ul formularului a fost folosit, dar eroarea a apărut de fapt în validarea PHP a câmpurilor, iar când formularul este trimis din nou, de data aceasta nu poate fi verificat și check_ajax_referer returnează -1, ceea ce nu este ceea ce dorim!

Solomon Closson Solomon Closson
26 oct. 2016 22:22:04