如果不允许这样做(并且我不能复制它),我现在如何拥有重复的用户条目?

时间:2019-12-19 作者:GWorking

在我的网站中,我有用户以电子邮件作为用户名注册

如果我试图手动添加一个新用户,而我使用的用户名(即邮件)已经存在,WP会投诉并告诉我该用户名已经注册

如果通过下面的代码以编程方式执行此操作,我会处理此问题并阻止注册

然而,我猜有一个用户多次单击表单提交按钮,成功地在我的WP用户数据库中复制了完全相同的用户

问题是,这怎么可能?

我该如何防止这种情况?

从评论中,换言之,重新表述问题:If I want to replicate this, how can I do this?

到目前为止,唯一有意义的解释是,我只能通过重新创建一个种族条件(frontend、postman、curl等等)来复制它

我不确定是否有办法阻止它(我想这就像防止DOS攻击一样)

我使用的代码是:

    public function register_user($request = null)
    {
        $response = array();
        $parameters = $request->get_json_params();
        $email = sanitize_text_field($parameters[\'email\']);
        $password = sanitize_text_field($parameters[\'password\']);
        $domain = $_SERVER[\'HTTP_REFERER\'];
        $uri = $_SERVER[\'REQUEST_URI\'];

        $role = \'subscriber\';
        $error = new WP_Error();

        if (empty($email)) {
            $error->add(...);
            return $error;
        }
        if (!is_email($email)) {
            $error->add(...);
            return $error;
        }
        if (empty($password)) {
            $error->add(...);
            return $error;
        }

        $user_id = username_exists($email);
        if (!$user_id && email_exists($email) == false) {
            $user_id = wp_create_user($email, $password, $email);

            if (!is_wp_error($user_id)) {
                $user = get_user_by(\'id\', $user_id);
                $user->set_role($role);
                $response[\'code\'] = 200;
                $response[\'message\'] = __("User \'" . $user_id . "\' ok ", "...");
            } else {
                return $user_id;
            }
        } else if ($user_id) {
            $error->add(...);
            return $error;
        }

        return new WP_REST_Response($response, 200);
    }
我只是不明白这是怎么发生的

从注释中编辑,包括前端代码

        <button type="text" value="" onClick={register}>
          <div>...</div>
        </button>
  const register = async e => {
    e.stopPropagation()
    if (registering === \'working\') return
    setRegistering(\'working\')

    let id = input_id.current.value.trim()
    let pass = input_pass.current.value
    let pass2 = input_pass2.current.value

    const success = pass === pass2 ? await auth.signup(id, pass, feedback) : \'not_equal_passwords\'
    setRegistering(success)
    if (success === \'yes\') navigate(\'/welcome\')
  }
  const signup = async (email, password) => {
    const register = `${domain}${website}/wp-json/.../register`
    const request = get_request(\'POST\')
    const expression = /^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+.)+([a-zA-Z0-9]{2,4})+$/

    let success = password ? (email ? \'\' : \'no_id\') : \'no_pass\'
    success = expression.test(String(email).toLowerCase()) ? \'\' : \'no_mail\'

    if (!success) {
      request.body = JSON.stringify({ email: email, password: password })
      const response = await get_response(register, request, 5, 4000, \'json\')
      success = response.code === \'user_exist\' ? \'user_exist\' : \'yes\'
    }

    return success
  }
const get_response = async (url, request, times, time = 2000, json) => {
  const rq = request || get_request(\'GET\')
  let response = await Promise.race([fetch(url, rq), wait(time)])
  let counter = 0

  for (const _ of [...Array(times)]) {
    if (counter >= times || response !== \'timed\') break
    response = await Promise.race([fetch(url, request), wait(time)])
    counter++
  }

  if (response === \'timed\') return
  return json ? await response.json() : response
}

1 个回复
最合适的回答,由SO网友:Tom J Nowell 整理而成

是的,WordPress会在内部检查重复的电子邮件,但不会检查重复的用户名

为了测试这一点,我通过wp shell:

wp_create_user( \'test\', \'password\', \'[email protected]\' );
第二次尝试的结果是:

=> class WP_Error#1962 (2) {
  public $errors =>
  array(1) {
    \'existing_user_email\' =>
    array(1) {
      [0] =>
      string(42) "Sorry, that email address is already used!"
    }
  }
  public $error_data =>
  array(0) {
  }
}
当我尝试用相同的用户名注册不同的电子邮件时:

wp> wp_create_user( \'test\', \'password\', \'[email protected]\' );
=> class WP_Error#1981 (2) {
  public $errors =>
  array(1) {
    \'existing_user_login\' =>
    array(1) {
      [0] =>
      string(36) "Sorry, that username already exists!"
    }
  }
  public $error_data =>
  array(0) {
  }
}
我还检查了代码,在wp_insert_user:

https://github.com/WordPress/WordPress/blob/f93ee2ca76164bcae721e4730c92ee1455fa1dd9/wp-includes/user.php#L1645-L1650

    /*
     * If there is no update, just check for `email_exists`. If there is an update,
     * check if current email and new email are the same, or not, and check `email_exists`
     * accordingly.
     */
    if ( ( ! $update || ( ! empty( $old_user_data ) && 0 !== strcasecmp( $user_email, $old_user_data->user_email ) ) )
        && ! defined( \'WP_IMPORTING\' )
        && email_exists( $user_email )
    ) {
        return new WP_Error( \'existing_user_email\', __( \'Sorry, that email address is already used!\' ) );
    }

那么是什么原因造成的呢

目前没有足够的信息来诊断这一问题,而且你的用户名是电子邮件也无济于事,因为这样很容易忽视事情,或者把它们当作总是一样的。

目前我最好的猜测是竞争条件,如果使用负载平衡,这种情况更有可能发生。

因此,即使它没有创建多个用户,您仍然需要处理用户双击或三次单击注册按钮,否则他们会得到一个错误,即电子邮件已经存在(因为他们得到的是第二个请求的结果,而第一个请求使用的是电子邮件)

检查用户查看代码的更可靠方法:

        $user_id = username_exists($email);
        if (!$user_id && email_exists($email) == false) {
            $user_id = wp_create_user($email, $password, $email);
        } else if ($user_id) {
            ... \'ERROR in Registration, user email already exists \' ...
            return $error;
        }
wp_create_user 已执行这些检查,并返回用户id或错误对象。在这种情况下:

如果用户创建失败,您的代码将永远无法发现这一点,因为没有对结果进行检查,因此,让我们简化它:

$user_id = wp_create_user( $email, $password, $email );
if ( is_wp_error( $user_id ) ) {
    // it failed!
    ....
    return $error;
}

请记住一些其他事项:

  • 不要将电子邮件用作用户名,自动生成它们,并在显示用户名时使用过滤器显示电子邮件
  • 清理!!!因为您只显示了一个受约束的代码片段,所以可能涉及到清理或缺少它,但由于您不会共享周围的代码,因此无法判断。可能是您的电子邮件后面有空格和其他字符,您不需要将电子邮件作为用户名来使用。WordPress将在登录时接受用户名和电子邮件,如果您登录,则无需知道用户名如果您知道电子邮件,则无需检查电子邮件是否存在,wp_insert_user 如果您的注册表单通过PHP表单提交而不是AJAX请求来运行,那么您的注册表单可能会更可靠,尽管您不能使其原子化,但您可以尝试减少步骤和并行请求的数量,在第一次单击时禁用注册按钮5秒钟,或者将其替换为进度微调器wp_create_user 呼叫wp_insert_user, 所以使用后者。WP有很多“中间人”的功能,试图提供帮助,但通常他们出于向后兼容的原因,有一些微妙的行为,这些行为更令人讨厌,而不是帮助。去掉这些中间环节可以简化事情,减少调试步骤,在注册端点上放置一个临时值,如果没有,那么您可以通过快速bash脚本和Curl,每分钟创建数千个用户