REST API에서 검색된 Nonce가 유효하지 않으며 wp_localize_script에서 생성 된 nonce와 다릅니다


10

Google에서 온 사용자의 경우 : 실제로 수행중인 작업을 모르면 REST API에서 nonces를 가져 오지 않아야합니다 . 나머지 API를 사용하여 쿠키 기반 인증 전용 의미 플러그인과 테마에 대해. 단일 페이지 애플리케이션의 경우 OAuth 를 사용해야합니다 .

이 질문은 단일 페이지 앱을 빌드 할 때 실제로 어떻게 인증해야하는지, JWT가 웹 앱에 적합하지 않으며, OAuth가 쿠키 기반 인증보다 구현하기가 어렵다는 문서가 명확하지 않거나 명확하지 않기 때문에 존재합니다.


핸드북 에는 Backbone JavaScript 클라이언트가 nonces를 처리하는 방법에 대한 예제가 있으며이 예제를 따르면 / wp / v2 / posts와 같은 내장 엔드 포인트가 수락하는 nonce를 얻습니다.

\wp_localize_script("client-js", "theme", [
  'nonce' => wp_create_nonce('wp_rest'),
  'user' => get_current_user_id(),

]);

그러나 Backbone을 사용하는 것은 의문의 여지가 없으며 테마도 마찬가지이므로 다음 플러그인을 작성했습니다.

<?php
/*
Plugin Name: Nonce Endpoint
*/

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => wp_create_nonce('wp_rest'),
        'user' => $user,
      ];
    },
  ]);

  register_rest_route('nonce/v1', 'verify', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      $nonce = !empty($_GET['nonce']) ? $_GET['nonce'] : false;
      return [
        'valid' => (bool) wp_verify_nonce($nonce, 'wp_rest'),
        'user' => $user,
      ];
    },
  ]);
});

JavaScript 콘솔에서 조금만 살펴보고 다음과 같이 썼습니다.

var main = async () => { // var because it can be redefined
  const nonceReq = await fetch('/wp-json/nonce/v1/get', { credentials: 'include' })
  const nonceResp = await nonceReq.json()
  const nonceValidReq = await fetch(`/wp-json/nonce/v1/verify?nonce=${nonceResp.nonce}`, { credentials: 'include' })
  const nonceValidResp = await nonceValidReq.json()
  const addPost = (nonce) => fetch('/wp-json/wp/v2/posts', {
    method: 'POST',
    credentials: 'include',
    body: JSON.stringify({
      title: `Test ${Date.now()}`,
      content: 'Test',
    }),
    headers: {
      'X-WP-Nonce': nonce,
      'content-type': 'application/json'
    },
  }).then(r => r.json()).then(console.log)

  console.log(nonceResp.nonce, nonceResp.user, nonceValidResp)
  console.log(theme.nonce, theme.user)
  addPost(nonceResp.nonce)
  addPost(theme.nonce)
}

main()

예상 결과는 두 개의 새 게시물이지만 Cookie nonce is invalid첫 번째 게시물에서 가져 오고 두 번째 게시물은 성공적으로 게시물을 만듭니다. 아마도 nonces가 다르기 때문일 것입니다. 왜 그렇습니까? 두 요청 모두에 동일한 사용자로 로그인했습니다.

여기에 이미지 설명을 입력하십시오

내 접근 방식이 잘못되면 nonce를 어떻게 받아야합니까?

편집 :

나는 많은 운이없이 지구본을 망치려고 노력했다 . wp_loaded 액션을 사용하여 조금 더 운이 좋았습니다.

<?php
/*
Plugin Name: Nonce Endpoint
*/

$nonce = 'invalid';
add_action('wp_loaded', function () {
  global $nonce;
  $nonce = wp_create_nonce('wp_rest');
});

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

  register_rest_route('nonce/v1', 'verify', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      $nonce = !empty($_GET['nonce']) ? $_GET['nonce'] : false;
      error_log("verify $nonce $user");
      return [
        'valid' => (bool) wp_verify_nonce($nonce, 'wp_rest'),
        'user' => $user,
      ];
    },
  ]);
});

위의 JavaScript를 실행하면 두 개의 게시물이 작성되지만 엔드 포인트 확인에 실패합니다!

여기에 이미지 설명을 입력하십시오

나는 wp_verify_nonce를 디버깅하러 갔다.

function wp_verify_nonce( $nonce, $action = -1 ) {
  $nonce = (string) $nonce;
  $user = wp_get_current_user();
  $uid = (int) $user->ID; // This is 0, even though the verify endpoint says I'm logged in as user 2!

로깅을 추가했습니다.

// Nonce generated 0-12 hours ago
$expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce'), -12, 10 );
error_log("expected 1 $expected received $nonce uid $uid action $action");
if ( hash_equals( $expected, $nonce ) ) {
  return 1;
}

// Nonce generated 12-24 hours ago
$expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
error_log("expected 2 $expected received $nonce uid $uid action $action");
if ( hash_equals( $expected, $nonce ) ) {
  return 2;
}

JavaScript 코드는 이제 다음 항목을 생성합니다. 보시다시피, 검증 엔드 포인트가 호출 될 때 uid는 0입니다.

[01-Mar-2018 11:41:57 UTC] verify 716087f772 2
[01-Mar-2018 11:41:57 UTC] expected 1 b35fa18521 received 716087f772 uid 0 action wp_rest
[01-Mar-2018 11:41:57 UTC] expected 2 dd35d95cbd received 716087f772 uid 0 action wp_rest
[01-Mar-2018 11:41:58 UTC] expected 1 716087f772 received 716087f772 uid 2 action wp_rest
[01-Mar-2018 11:41:58 UTC] expected 1 716087f772 received 716087f772 uid 2 action wp_rest

답변:


3

자세히 살펴보십시오 function rest_cookie_check_errors().

를 통해 nonce를 받으면 /wp-json/nonce/v1/get처음에 nonce를 보내지 않습니다. 따라서이 함수는 다음 코드를 사용하여 인증을 무효화합니다.

if ( null === $nonce ) {
    // No nonce at all, so act as if it's an unauthenticated request.
    wp_set_current_user( 0 );
    return true;
}

그렇기 때문에 REST 호출에서 다른 nonce를 얻는 것과 테마에서 얻는 것입니다. get 요청에서 유효한 nonce를 보내지 않았기 때문에 REST 호출이 의도적으로 로그인 신임 정보 (이 경우 쿠키 인증을 통해)를 인식하지 못합니다.

이제 wp_loaded 코드가 작동하는 이유는 nonce를 가져 와서이 나머지 코드가 로그인을 무효화하기 전에 전역에 저장했기 때문입니다. 확인이 수행되기 전에 나머지 코드가 로그인을 무효화하기 때문에 확인에 실패합니다.


나는 그 기능을 보지 않았지만 아마도 의미가 있습니다. 문제는 GET 요청에 유효한 nonce를 포함시켜야하는 이유는 무엇입니까? (지금은 얻었지만 분명하지는 않습니다.) / verify 엔드 포인트의 요점은 nonce가 여전히 유효한지 확인할 수 있고 그것이 오래되거나 유효하지 않은 경우 새로운 nonce를 얻는 것입니다.
Christian

rest_cookie_check_errors의 소스를 기반으로 엔드 포인트가 변경되지 않고 $_GET['nonce']nonce 헤더 또는 $_GET['_wpnonce']매개 변수 가되도록 엔드 포인트를 변경해야합니다 . 옳은?
Christian

1

이 솔루션은 작동하지만 권장되지 않습니다 . OAuth가 선호되는 선택입니다.


알았어요

나는 생각 wp_get_current_user 적절한 사용자 개체의 취득에 실패로, 그 wp_verify_nonce이 나뉩니다.

오토 (Otto)가 설명하는 것과는 다릅니다.

다행히 필터가 있습니다. $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );

이 필터를 사용하여 다음을 작성할 수 있었고 JavaScript 코드는 다음과 같이 실행됩니다.

여기에 이미지 설명을 입력하십시오

<?php
/*
Plugin Name: Nonce Endpoint
*/

$nonce = 'invalid';
add_action('wp_loaded', function () {
  global $nonce;
  $nonce = wp_create_nonce('wp_rest');
});

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

  register_rest_route('nonce/v1', 'verify', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      $nonce = !empty($_GET['nonce']) ? $_GET['nonce'] : false;
      add_filter("nonce_user_logged_out", function ($uid, $action) use ($user) {
        if ($uid === 0 && $action === 'wp_rest') {
          return $user;
        }

        return $uid;
      }, 10, 2);

      return [
        'status' => wp_verify_nonce($nonce, 'wp_rest'),
        'user' => $user,
      ];
    },
  ]);
});

수정 프로그램에서 보안 문제를 발견하면 소리 지르십시오. 현재 글로벌 이외의 다른 문제는 볼 수 없습니다.


0

이 모든 코드를 보면 문제가 클로저를 사용하는 것 같습니다. 에서 init단계 만 후크를 설정해야하고 모든 코어로 데이터를 평가하지 적재를 끝낼 초기화된다.

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

$user클로저에 사용되도록 초기에 바인딩되어 있지만 쿠키가 이미 처리되었고 사용자가 쿠키를 기반으로 인증되었다는 약속은 없습니다. 더 나은 코드는

add_action('rest_api_init', function () {
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () {
    $user = get_current_user_id();
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

항상 워드 프레스에 훅이있는 것처럼 항상 최신 훅을 사용하고 필요없는 것을 미리 계산하지 마십시오.


set_current_user가 init 및 after_setup_theme 이전에 실행되는 순서와 순서를 파악하기 위해 Query Monitors 조치 및 후크 섹션을 사용했습니다.
Christian

@Christian, 그리고 그들 모두는 json API와 관련이 없을 수도 있습니다. 이 상황에서 쿼리 모니터가 작동하면 놀랍습니다
Mark Kaplun
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.