วิธีรับ nonce ที่ไม่ซ้ำสำหรับคำขอ Ajax แต่ละรายการ


11

ฉันได้เห็นการถกเถียงกันสองสามครั้งเกี่ยวกับการทำให้ Wordpress สร้างความแปลกใหม่ให้กับอาแจ็กซ์ที่ร้องขอ แต่สำหรับชีวิตของฉันฉันไม่สามารถทำให้ Wordpress ทำมันได้ - ทุกครั้งที่ฉันขอสิ่งที่ฉันคิดว่าควรจะเป็นใหม่ ไม่ใช่ฉันได้รับ nonce เดียวกันกลับมาจาก Wordpress ฉันเข้าใจแนวคิดของ nonce_life ของ WP และแม้กระทั่งตั้งค่าเป็นอย่างอื่น แต่นั่นก็ไม่ได้ช่วยฉัน

ฉันไม่ได้สร้าง nonce ในวัตถุ JS ในส่วนหัวผ่านการแปลเป็นภาษาท้องถิ่น - ฉันทำในหน้าแสดงผลของฉัน ฉันสามารถทำให้เพจของฉันประมวลผลคำขอ Ajax ได้ แต่เมื่อฉันร้องขอ nonce ใหม่จาก WP ในการติดต่อกลับฉันได้รับ nonce เดียวกันกลับมาและฉันไม่รู้ว่าฉันทำอะไรผิด ... ในที่สุดฉันต้องการ ขยายสิ่งนี้เพื่อให้สามารถมีหลายรายการในหน้าแต่ละรายการมีความสามารถในการเพิ่ม / ลบ - ดังนั้นฉันต้องการโซลูชันที่จะอนุญาตคำขอ Ajax ที่ตามมาหลายรายการจากหน้าเดียว

(และฉันควรจะบอกว่าฉันได้ใส่ฟังก์ชั่นทั้งหมดนี้ลงในปลั๊กอินดังนั้น "หน้าจอแสดงผล" ส่วนหน้าจึงเป็นฟังก์ชั่นที่มาพร้อมกับปลั๊กอิน ... )

function.php: แปลเป็นภาษาท้องถิ่น แต่ฉันไม่ได้สร้าง nonce ที่นี่

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

โทรหา 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
         }
      }
  });
});

รับ 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 ในหมู่ที่:

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

ณ จุดนี้ฉันรู้สึกขอบคุณจริง ๆสำหรับปมหรือพอยน์เตอร์ใด ๆในการรับ WP เพื่อสร้าง nonce ที่ไม่ซ้ำกันสำหรับคำขอ Ajax ใหม่แต่ละครั้ง ...


อัปเดต: ฉันแก้ไขปัญหาแล้ว ตัวอย่างโค้ดด้านบนนั้นถูกต้อง แต่ฉันเปลี่ยนการสร้าง $ newNonce ในการเรียกกลับ PHP เพื่อผนวกสตริง microseconds เพื่อให้แน่ใจว่ามันจะไม่ซ้ำกันในคำขอ Ajax ที่ตามมา


จากรูปลักษณ์ที่สั้นมาก : คุณกำลังสร้าง nonce หลังจากที่คุณได้รับมัน (บนหน้าจอ)? ทำไมคุณไม่สร้างระหว่างการโทรแบบโลคัลไลซ์
ไกเซอร์

jQuery กำลังใช้ nonce เริ่มต้นจากแอ็ตทริบิวต์ "data-nonce" ในลิงก์ # myelement และแนวคิดก็คือหน้านั้นสามารถประมวลผลได้โดย Ajax หรือโดยตัวมันเอง สำหรับฉันแล้วดูเหมือนว่าการสร้าง nonce ครั้งเดียวผ่านการโทรแบบโลคัลไลซ์จะแยกออกจากการประมวลผลที่ไม่ใช่ JS แต่ฉันอาจผิดไป ทั้งสองวิธี Wordpress ให้ฉันกลับ nonce เดียวกัน ...
ทิม

นอกจากนี้: การไม่วาง nonce ในการโทรแบบโลคัลทำให้ไม่สามารถมีหลายไอเท็มบนหน้าซึ่งแต่ละไอเท็มอาจมี nonce ที่ไม่ซ้ำกันสำหรับคำขอ Ajax หรือไม่?
ทิม

การสร้าง nonce ภายใน localize จะสร้างและทำให้พร้อมใช้งานสำหรับหนึ่งสคริปต์ แต่คุณสามารถเพิ่มจำนวน จำกัด (อื่น ๆ ที่มีชื่อคีย์) ให้เป็นค่าท้องถิ่นด้วยการแยก nonces
ไกเซอร์

หากคุณแก้ไขได้คุณควรโพสต์คำตอบและทำเครื่องหมายว่า "ยอมรับ" มันจะช่วยให้เว็บไซต์จัดระเบียบ ฉันแค่ล้อเล่นกับโค้ดของคุณและมีสองสิ่งที่ไม่ได้ผลสำหรับฉันดังนั้นสองเท่าที่คำขอให้คุณโพสต์โซลูชันของคุณ
s_ha_dum

คำตอบ:


6

ต่อไปนี้เป็นคำตอบที่ยาวมากสำหรับคำถามของฉันซึ่งเกินกว่าจะตอบคำถามที่สร้าง nonces ที่ไม่ซ้ำสำหรับคำขอ Ajax ที่ตามมา นี่เป็นคุณสมบัติ "เพิ่มในรายการโปรด" ที่ทำขึ้นเพื่อวัตถุประสงค์ทั่วไปของคำตอบ (คุณสมบัติของฉันช่วยให้ผู้ใช้เพิ่มรหัสโพสต์ของไฟล์แนบรูปภาพในรายการโปรด แต่อาจนำไปใช้กับคุณสมบัติอื่น ๆ ที่หลากหลาย อาแจ็กซ์) ฉันเขียนโค้ดนี้เป็นปลั๊กอินแบบสแตนด์อโลนและมีบางรายการขาดหายไป - แต่นี่ควรมีรายละเอียดเพียงพอที่จะให้ส่วนสำคัญถ้าคุณต้องการทำซ้ำคุณสมบัติ มันจะทำงานในแต่ละโพสต์ / หน้า แต่มันจะทำงานในรายการโพสต์ (เช่นคุณสามารถเพิ่ม / ลบรายการในรายการโปรดแบบอินไลน์ผ่าน Ajax และแต่ละโพสต์จะมี nonce ที่ไม่ซ้ำกันของมันสำหรับแต่ละคำขอ Ajax) โปรดทราบว่ามี

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 (มีการแก้ไขข้อบกพร่องมากมายที่สามารถลบออกได้)

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

ฟังก์ชั่น (หน้าจอแสดงผลและการกระทำ Ajax)

หากต้องการส่งออกลิงก์เพิ่ม / ลบรายการโปรดเพียงโทรไปที่หน้า / โพสต์ของคุณผ่าน:

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

ฟังก์ชั่นการแสดงผล Front-end:

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:

/**
* 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

ฉันต้องตั้งคำถามถึงเหตุผลเบื้องหลังการได้รับ nonce ใหม่สำหรับคำขออาแจ็กซ์แต่ละครั้ง Nonce ดั้งเดิมจะหมดอายุ แต่สามารถใช้ได้มากกว่าหนึ่งครั้งจนกว่าจะมี การใช้จาวาสคริปต์จะได้รับผ่าน Ajax เอาชนะวัตถุประสงค์โดยเฉพาะในกรณีที่เกิดข้อผิดพลาด (จุดประสงค์ของการ nonces เป็นการรักษาความปลอดภัยเล็กน้อยสำหรับการเชื่อมโยงการดำเนินการกับผู้ใช้ภายในกรอบเวลา)

ฉันไม่ควรพูดถึงคำตอบอื่น ๆ แต่ฉันใหม่และไม่สามารถแสดงความคิดเห็นข้างต้นดังนั้นในส่วนที่เกี่ยวกับ "วิธีแก้ปัญหา" ที่โพสต์คุณจะได้รับข้อความใหม่ทุกครั้ง แต่ไม่ได้ใช้ในคำขอ แน่นอนว่ามันคงเป็นเรื่องยากที่จะได้ microseconds เหมือนกันทุกครั้งเพื่อจับคู่ nonce ใหม่ที่สร้างขึ้นในแบบนั้น รหัส PHP กำลังตรวจสอบกับ nonce ดั้งเดิมและจาวาสคริปต์กำลังส่ง nonce ดั้งเดิม ... ดังนั้นจึงใช้งานได้ (เพราะยังไม่หมดอายุ)


1
ปัญหาคือ nonce หมดอายุหลังจากที่ได้รับการใช้งานและจะกลับ -1 ในฟังก์ชั่น ajax หลังจากแต่ละครั้ง ปัญหานี้เป็นปัญหาหากคุณตรวจสอบความถูกต้องบางส่วนของแบบฟอร์มใน PHP และส่งคืนข้อผิดพลาดในการพิมพ์ออกมา ฟอร์มที่ไม่ได้ใช้ถูกนำมาใช้ แต่เกิดข้อผิดพลาดจริงในการตรวจสอบความถูกต้องของ php และเมื่อฟอร์มได้รับการส่งอีกครั้งคราวนี้จะไม่สามารถตรวจสอบและcheck_ajax_refererส่งกลับ -1 ซึ่งไม่ใช่สิ่งที่เราต้องการ!
โซโลมอน Closson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.