Cum să obții un nonce unic pentru fiecare cerere Ajax?
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.
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');

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ă).

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!
