각 Ajax 요청에 대해 고유 한 nonce를 얻는 방법?


11

나는 후속 Ajax 요청에 대해 Wordpress가 고유 한 nonce를 재생성하는 방법에 대한 몇 가지 토론을 보았지만, 내 인생에서 실제로 Wordpress가 그것을 할 수는 없습니다. nonce, Wordpress에서 동일한 nonce를 다시 얻습니다. WP의 nonce_life의 개념을 이해하고 심지어 다른 것으로 설정했지만 도움이되지 않았습니다.

현지화를 통해 헤더의 JS 객체에서 nonce를 생성하지 않습니다-디스플레이 페이지에서 수행합니다. 내 페이지가 Ajax 요청을 처리하도록 할 수는 있지만 콜백에서 WP로부터 새로운 nonce를 요청하면 동일한 nonce가 반환되고, 내가 뭘 잘못하고 있는지 모르겠다 ... 궁극적으로 페이지에 여러 항목이 추가 될 수 있도록이를 확장하십시오. 각 항목에는 추가 / 제거 기능이 있으므로 한 페이지에서 여러 후속 Ajax 요청을 허용하는 솔루션이 필요합니다.

(그리고 나는이 기능을 모두 플러그인에 넣었으므로 프론트 엔드 "디스플레이 페이지"는 실제로 플러그인에 포함 된 기능입니다 ...

functions.php : localize, 여기에 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();
}

프론트 엔드 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를 얻기에 단서 또는 포인터 ...


업데이트 : 내 문제를 해결했습니다. 위의 코드 스 니펫은 유효하지만 PHP 콜백에서 $ newNonce 생성을 변경하여 후속 Ajax 요청에서 고유하도록 마이크로 초 문자열을 추가했습니다.


A로부터 매우 간단한 모양 : 당신이 (디스플레이)를받은 후 당신은 비표를 만드는? 지역 통화 중에 왜 생성하지 않습니까?
카이저

jQuery는 a # myelement 링크의 "data-nonce"속성에서 초기 nonce를 사용하고 있으며, 페이지를 Ajax 또는 자체적으로 처리 할 수 ​​있다는 아이디어가 있습니다. localize 호출을 통해 nonce를 한 번 생성하면 non-JS 처리에서 제외되는 것으로 보이지만 잘못 생각할 수 있습니다. 어느 쪽이든 Wordpress는 나에게 같은 nonce를 돌려줍니다 ...
Tim

또한 : 현지화 호출에 nonce를 넣지 않으면 각 항목이 Ajax 요청에 대해 고유 한 nonce를 가질 수있는 페이지에 여러 항목을 가질 수 없습니까?
Tim

localize 내부에 nonce를 만들면 해당 스크립트 하나를 생성하여 사용할 수있게됩니다. 그러나 별도의 nonces로 무제한의 다른 (키 이름) 현지화 값을 추가 할 수도 있습니다.
카이저

해결 한 경우 답변을 게시하고 "허용됨"으로 표시하는 것이 좋습니다. 사이트를 체계적으로 유지하는 데 도움이됩니다. 나는 당신의 코드를 엉망으로 만들었고 몇 가지 일이 나를 위해 작동하지 않으므로 솔루션을 게시하라는 요청을 두 배로하십시오.
s_ha_dum

답변:


6

다음은 후속 Ajax 요청에 대해 고유 한 논스를 생성하는 문제를 해결하는 것 이상의 내 자신의 질문에 대한 긴 답변입니다. 이것은 답변을 위해 일반적으로 만들어지는 "즐겨 찾기에 추가"기능입니다 (내 기능을 사용하면 사진 첨부 파일의 게시 ID를 즐겨 찾기 목록에 추가 할 수 있지만이 기능은 의존하는 다양한 다른 기능에 적용될 수 있습니다. 아약스). 나는 이것을 독립형 플러그인으로 코딩했으며 몇 가지 항목이 누락되었지만 기능을 복제하려는 경우 요지를 제공하기에 충분한 세부 정보가 있어야합니다. 개별 게시물 / 페이지에서 작동하지만 게시물 목록에서도 작동합니다 (예 : Ajax를 통해 즐겨 찾기에 항목을 추가 / 제거 할 수 있으며 각 게시물에는 각 Ajax 요청에 대해 고유 한 nonce가 있습니다). 있음을 명심하십시오.

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

favorite.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);
}

프론트 엔드 디스플레이 기능 :

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

}

아약스 액션 기능 :

/**
* 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는 만료되지만 만료 될 때까지 두 번 이상 사용할 수 있습니다. 자바 스크립트가 아약스를 통해 그것을 받으면 목적을 무효화하며, 특히 오류 사례에 제공합니다. (nonces의 목적은 작업을 시간 프레임 내에서 사용자와 연관시키기위한 작은 보안입니다.)

나는 다른 답변을 언급하지는 않지만 새롭고 위에서 언급 할 수 없으므로 게시 된 "솔루션"과 관련하여 매번 새로운 nonce를 받고 있지만 요청에 사용하지 않습니다. 그런 식으로 생성 된 새로운 nonce와 일치 할 때마다 마이크로 초를 동일하게 얻는 것이 확실히 까다로울 것입니다. PHP 코드는 원래 nonce에 대해 확인 중이며 javascript는 원래 nonce를 제공하고 있습니다 ... 그래서 만료되지 않았기 때문에 작동합니다.


1
문제는 nonce가 사용 된 후에 만료되고 매번 후에 ajax 함수에서 -1을 반환한다는 것입니다. PHP에서 양식의 일부를 확인하고 오류를 반환하여 인쇄하는 경우 문제가됩니다. nonce 형식이 사용되었지만 실제로는 필드의 PHP 유효성 검사에서 오류가 발생했으며 이번에는 양식이 다시 제출되면 확인할 수 없으며 check_ajax_referer-1을 반환합니다. 이는 우리가 원하는 것이 아닙니다!
Solomon Closson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.