从REST API检索到的随机数无效,并且与wp_localize_script中生成的随机数不同


10

对于那些来自Google的应用程序:除非您真的知道自己在做什么,否则您可能不应该从REST API中获取现时信息。REST API的基于Cookie的身份验证仅适用于插件和主题。对于单页应用程序,您可能应该使用OAuth

之所以存在这个问题,是因为文档尚不清楚/尚不清楚在构建单页应用程序时应如何进行真正的身份验证,JWT并不真正适用于Web应用程序,并且比基于cookie的身份验证更难以实现OAuth。


该手册中有一个有关Backbone JavaScript客户端如何处理随机数的示例,如果我按照该示例进行操作,则会得到一个随机数,该内置数可以被/ wp / v2 / posts等端点接受。

\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从第一个职位获得,第二个职位成功创建了该职位。那可能是因为随机数不同,但是为什么呢?我在两个请求中均以同一用户身份登录。

在此处输入图片说明

如果我的方法是错误的,我应该如何获得随机数?

编辑

没有多大运气就试图与全球人士混为一谈。通过使用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

Answers:


3

仔细看看function rest_cookie_check_errors()

当您通过获得随机数时/wp-json/nonce/v1/get,您并不是一开始就发送随机数。因此,此函数使用以下代码使身份验证无效:

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

这就是为什么您从REST调用中获得不同的随机数,而不是从主题中获得它的原因。REST调用有意不识别您的登录凭据(在这种情况下是通过cookie auth),因为您没有在get请求中发送有效的随机数。

现在,您的wp_loaded代码起作用的原因是因为在此其余代码使您的登录无效之前,您获得了随机数并将其保存到全局变量。验证失败,因为其余代码会在验证之前使您的登录无效。


我什至没有看过那个功能,但这可能是有道理的。问题是,为什么我应该为GET请求包括有效的随机数?(我现在知道了,但是还很不明显)/ verify端点的全部要点是,我可以检查随机数是否仍然有效,以及它是否陈旧或无效,请获取一个新的随机数。
基督教徒

根据rest_cookie_check_errors的来源,我应该更改端点,以便它不检查$_GET['nonce'],而是现时标头或$_GET['_wpnonce']参数。正确?
基督教徒

1

尽管此解决方案有效,但不建议这样做。OAuth是首选。


我想我明白了。

认为 wp_verify_nonce已损坏,因为wp_get_current_user无法获取正确的用户对象。

正如奥托(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,
      ];
    },
  ]);

与wordpress中的任何钩子一样,尽可能使用最新的钩子,切勿尝试预先计算不需要的任何东西。


我使用了“查询监视器操作和钩子”部分来确定运行什么和以什么顺序运行,set_current_user在init和after_setup_theme之前运行,在$ user之外和闭包之前定义$ user应该不会有问题。
基督教徒

@Christian,所有这些都可能与json API的上下文无关。如果查询监视器在这种情况下起作用,我会感到非常惊讶
Mark Kaplun '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.