使用更新的代码进行编辑:仍然没有解决方案
有人能发现这段代码中的错误吗?
自定义页面.php:
<form name="customForm">
<?php wp_nonce_field('code_check', 'codecheck'); ?>
Validation Code:<br>
<input type="password" name="inputcode" id="inputcode" maxlength="6" inputmode="numeric">
<input type="text" name="message" id="message" style="display:none; background-color: #FFCCCC;"><br>
<input type="button" name="submitbutton" value="Submit" onClick="customfunction()">
</form>
自定义.js:
function customfunction() {
const userInput = document.addStamp.inputcode.value;
const token = document.addStamp.codecheck.value;
fetch(`http://...../wp-json/api/v1/custom?code=${userInput}&token=${token}`).then(r => r.json()).then(data => {
......
API文件.php:
public function custom($request)
{
$params = $request->get_params();
$retrieved_nonce = $params[token];
if($retrieved_nonce) {
if (!wp_verify_nonce($retrieved_nonce, 'code_check' ) ) die( 'Failed security check' );
}
....
一切正常,直到我将 nonce 验证代码添加到 api 请求中。
现在,当我单击“提交”按钮时,它不提交,我进入控制台:
Uncaught (in promise) SyntaxError: Unexpected token F in JSON at position 0
所以它失败了,因为“F”是失败消息的点 0。
但是,如果我输出“$retrieved_nonce”,我实际上会得到页面源代码中所示的随机数值,所以看起来它正在到达端点?
我尝试注销并重新登录,但没有任何变化。
我这个代码设置错了吗?
你可以尝试一下。
表格。
<form name="customForm" method="post">
<?php wp_nonce_field('code_check', 'code_check'); ?>
Validation Code:<br>
<input type="password" name="inputcode" id="inputcode" maxlength="6" inputmode="numeric">
<input type="text" name="message" id="message" style="display:none; background-color: #FFCCCC;"><br>
<input type="button" name="submitbutton" value="Submit" onClick="customfunction()">
</form>
在 customfunction 函数中还发送 wp_nonce_field 字段值。
function customfunction() {
const userInput = document.customForm.inputcode.value;
const code_check = document.customForm.code_check.value;
fetch('http://...../wp-json/api/v1/custom?code='+userInput+'&code_check'+code_check).then(r => r.json()).then(data => {
现在验证 wp_nonce_field 字段值
public function custom($request)
{
$retrieved_nonce = $request['code_check'];
if (!wp_verify_nonce($retrieved_nonce, 'code_check' ) ) die( 'Failed
security check' );
/***
you can also try
if ( isset( $request['code_check'] ) || wp_verify_nonce( $request['code_check'], 'code_check' ) )
*******/
}
我遇到了同样的问题,在调试时,我发现随机数与当前登录的用户绑定在一起(这显然对安全性很有好处)。
但是,当我创建随机数时,它无法正确识别我已登录。就我而言,我在其上创建随机数的表单页面是一个自定义端点,使用
add_rewrite_endpoint
并路由到我的自定义通过 template_include
钩子模板。我不知道为什么 WordPress 在查看前端时没有检测到我已登录,因为用户身份验证挂钩在模板挂钩之前运行,但这是另一天的问题。
当 JS 使用该值将该请求发送到后端时,就会出现差异。在后端,它确实知道我已登录,然后使用这些值重新创建随机数哈希。哈希比较失败,因为发送的哈希使用的值与我的用户帐户中的值不同。
我实现的解决方案类似于摘要身份验证,用于创建不与用户帐户绑定的模拟随机数值。所以我创建了这个 PHP 类:
class CustomNonce
{
private static $endpoint = 'authorization-uri-endpoint'; // E.g., `auth` from `https://example.com/auth/`
private static $username = 'SomeArbitr@ryT3xt';
private static $password = 'Some0therArb1traryText';
/**
* Required Parts of Digest
*
* Feel free to modify the array order to further randomize it from other sites
*
* @var array An array of the 9 required parts of digest authentication.
*/
private static $digest_parts = array('username', 'realm', 'nonce', 'cnonce', 'nc', 'qop', 'opaque', 'uri', 'response');
/**
* Digest Delimiter
*
* Feel free to modify this character to further randomize it from other sites
*
* @var string A string used as a delimiter for digest authentication.
*/
private static $digest_delimiter = '=^_^=';
/**
* Creates nonce.
*
* @param string|int $action Scalar value to add context to the nonce.
*
* @return string The token.
*/
public static function create($action = -1)
{
$_key = trim(sanitize_key('_nonce_' . self::$endpoint . '_' . $action . '_'));
$nc_key = $_key . 'nc';
$nc = get_transient($nc_key); // Get the current count for this particular request
if (!is_int($nc)) {
$nc = 1; // Start it at one if it was never called before
} else {
$nc++; // Increment it
}
set_transient($nc_key, $nc); // Save it without an expiration to make it persistant cache
$digest = array();
foreach (self::$digest_parts as $key) {
switch ($key) {
case 'username': // username, same as server response
$digest[$key] = self::$username;
break;
case 'realm': // realm, same as server response
$digest[$key] = self::$endpoint;
break;
case 'nonce': // nonce, generated by remote server to return back as is, the "signature" if you will
$digest[$key] = $action;
break;
case 'cnonce': // cnonce, random unique ID generated by client
$digest[$key] = md5(uniqid($nc_key . $action, true));
set_transient($_key . $nc, $digest[$key], HOUR_IN_SECONDS); // Save it for just 1 hour
break;
case 'nc': // nonce count, hexadecimal serial number for the request, the client should increase this number by one for every request
$digest[$key] = sprintf('%06d', $nc); // Convert it to a 6-digit number (hexadecimal), probably with preceeding zeroes
break;
case 'qop': // qop, values are "auth" or "auth-int"
$digest[$key] = 'auth-int';
break;
case 'opaque': // opaque, same as server response
$digest[$key] = self::$password;
break;
case 'uri': // encoded authentication uri, where authentication should take place
$digest[$key] = htmlentities(urlencode(home_url(self::$endpoint)), ENT_QUOTES, 'UTF-8', false);
break;
default:
$digest[$key] = '';
break;
}
}
/**
* Validation hash.
*
* The server will also make one and compare it to this.
*/
$digest['response'] = self::generate_hash($digest['realm'], $digest['uri'], $digest['nonce'], $digest['cnonce'], $digest['username'], $digest['opaque'], $digest['nc'], $digest['qop']);
/**
* Since we aren't sending it as an Authorization header, we don't need to configure it like this:
* `Digest username="%s",realm="%s",nonce="%s",cnonce="%s",nc="%s",qop="%s",opaque="%s",uri="%s",response="%s"`
*/
return implode(self::$digest_delimiter, array_values($digest));
}
public static function verify($nonce, $action = -1)
{
$nonce = trim(strval($nonce));
$action = trim(sanitize_text_field($action));
$request_method = strtoupper(sanitize_key(array_has_key('REQUEST_METHOD', $_SERVER)));
if (empty($nonce) || empty($action) || $request_method !== 'POST') {
error_log('Error. Nonce was empty, action was empty, or invalid request method.' . print_r(array(
'nonce' => $nonce,
'action' => $action,
'method' => $request_method
), true));
return false;
}
/**
* Parse the digest value
*
* Protect against missing data
*/
$digest = explode(self::$digest_delimiter, $nonce);
if (count($digest) !== count(self::$digest_parts)) {
error_log('Error. Could not parse digest. ' . print_r(array(
'nonce' => $nonce,
'digest' => $digest,
), true));
return false;
}
$digest = array_combine(self::$digest_parts, $digest);
/**
* Validate Requirements
*
* Ensure all parts exist and are valid.
*/
$nc = 0;
$cnonce = '';
foreach ($digest as $key => $value) {
$value = trim(sanitize_text_field($value));
if (empty($value)) {
error_log('Error. One or more required parts of the digest are missing.' . PHP_EOL . print_r(array(
'key' => $key,
'value' => $value,
'digest' => $digest,
'nonce' => $nonce
), true));
return false;
}
if ($key == 'nc') {
/**
* Compare nonce counts and ensure it was issued at all.
*/
$digest[$key] = intval($value);
if (!$digest[$key]) {
error_log('Error. Missing nc.' . PHP_EOL . print_r(array(
'key' => $key,
'value' => $value,
'digest' => $digest,
'nonce' => $nonce
), true));
return false;
}
// $_key = trim(sanitize_key('_nonce_' . self::$endpoint . '_' . $action . '_'));
// $nc_key = $_key . 'nc'; // E.g., "_nonce_magic-mirror_hourly-update_nc"
$_key = trim(sanitize_key('_nonce_' . self::$endpoint . '_' . $action . '_'));
$_dkey = trim(sanitize_key('_nonce_' . $digest['realm'] . '_' . $digest['nonce'] . '_'));
if ($_key !== $_dkey) {
error_log('Error. Mismatching nonce keys.' . PHP_EOL . print_r(array(
'key' => $key,
'value' => $value,
'key1' => $_key,
'key2' => $_dkey,
'digest' => $digest,
'nonce' => $nonce
), true));
return false;
}
$nc = intval(sanitize_text_field(get_transient($_key . 'nc')));
if ($nc < $digest[$key]) {
error_log('Error. Mismatching nc.' . PHP_EOL . print_r(array(
'key' => $key,
'value' => $value,
'nc' => $nc,
'_key' => $_key,
'digest' => $digest,
'nonce' => $nonce
), true));
return false;
}
$cnonce = trim(sanitize_text_field(get_transient($_key . $nc)));
if (!$cnonce || $cnonce !== $digest['cnonce']) {
error_log('Error. Invalid or expired cnonce.' . PHP_EOL . print_r(array(
'key' => $key,
'value' => $value,
'_key' => $_key . $nc,
'cnonce' => $cnonce,
'nc' => $nc,
'digest' => $digest,
'nonce' => $nonce
), true));
return false;
}
} elseif ($key == 'uri' && $value === 'http://') {
error_log('Error. Invalid URI.' . PHP_EOL . print_r(array(
'key' => $key,
'value' => $value,
'digest' => $digest,
'nonce' => $nonce
), true));
return false;
} elseif ($key == 'qop' && $value != 'auth-int') {
error_log('Error. Invalid authorization method' . PHP_EOL . print_r(array(
'key' => $key,
'value' => $value,
'digest' => $digest,
'nonce' => $nonce
), true));
return false;
}
$digest[$key] = $value;
}
/*
* Verify Digest
*
* Make our own hash and compare it with the one sent to us.
*/
$hash = self::generate_hash(
self::$endpoint, // Realm
htmlentities(urlencode(home_url(self::$endpoint)), ENT_QUOTES, 'UTF-8', false), // URI
$digest['nonce'], // Nonce from client
$cnonce, // My nonce
self::$username, // My username
self::$password, // My password
$digest['nc'], // The hexidecimal value provided to me
$digest['qop'] // The auth method provided to me
);
if ($hash !== $digest['response']) {
error_log('Failed digest authentication.' . PHP_EOL . print_r(array(
'hash' => $hash,
'response' => $digest['response'],
'digest' => $digest
), true));
return false;
}
// Valid nonce.
return true;
}
/**
* Get Valid Digest Response
*
* @param string $realm The realm, provided by this application.
* @param string $uri The URL where authentication takes place, provided by the remote request.
* @param string $nonce Randomly generated string provided by the remote request.
* @param string $cnonce Randomly generated string provided by this application.
* @param string $username The username that matches the one in the application, provided by the remote request.
* @param string $password The password that matches the one in the application, provided by the remote request.
* @param string $nc Nonce count, hexadecimal serial number for the request, the application should increase this number by one for every request.
* @param string $qop Values are `auth` or `auth-int`.
* @param string $method Request method.
*
* @param string
*/
private static function generate_hash($realm, $uri, $nonce, $cnonce, $username, $password, $nc, $qop = 'auth-int', $method = 'POST')
{
// Exit early if missing required fields
if (!$realm || !$uri || $uri == 'http://' || !$nonce || !$cnonce || !$username || !$nc || !$qop || !$method) {
return '';
}
$before = md5($username . ':' . $realm . ':' . $password);
$after = md5($method . ':' . $uri);
return md5($before . ':' . $nonce . ':' . $nc . ':' . $cnonce . ':' . $qop . ':' . $after);
}
使用时请更换
<?php wp_nonce_field('code_check', 'codecheck'); ?>
与
<input type="hidden" name="_wpnonce" value="<?php echo esc_attr(CustomNonce::create('code_check')); ?>">
并更换
wp_verify_nonce($retrieved_nonce, 'code_check' )
与
CustomNonce::verify($retrieved_nonce, 'code_check')
如果您不想记录失败,也可以删除所有
error_log()
行。我将它们保留在我的开发环境中进行测试,但随后将它们删除以用于生产。