使用WordPress REST API通过AJAX更改密码

Xan*_*der 8 php cookies wordpress nonce wordpress-rest-api

我正在使用WordPress REST API构建密码更改表单.用户输入一个新密码,然后通过AJAX提交给自定义端点,该端点执行以下操作:

$userID = get_current_user_id();
wp_set_password($password, $userID);

//Log user in and update auth cookie
wp_set_current_user($userID);
wp_set_auth_cookie($userID);
//Set the cookie immediately
$_COOKIE[AUTH_COOKIE] = wp_generate_auth_cookie($userID, 2 * DAY_IN_SECONDS);

//Return fresh nonce
return new WP_Rest_Response(array(
    'nonce' => wp_create_nonce('wp_rest')
));
Run Code Online (Sandbox Code Playgroud)

端点应自动将用户登录并返回新的nonce,以便他们不必再使用新密码登录.

问题是返回的nonce完全相同且无效.我只能在页面刷新后获得新的nonce.似乎WordPress依赖于生成随机数的一些$_COOKIE$_SESSION变量在页面刷新之前不会更新.

谢谢你的帮助.

Sal*_* CJ 9

似乎WordPress依赖于生成随机数的一些$ _COOKIE或$ _SESSION变量在页面刷新之前不会更新.

是的,它是LOGGED_IN_COOKIE,默认为:

'wordpress_logged_in_' . COOKIEHASH
Run Code Online (Sandbox Code Playgroud)

看一眼:

所以你要替换这段代码:(我不会wp_generate_auth_cookie()用于此目的;而是使用已经生成的内容wp_set_auth_cookie())

//Set the cookie immediately
$_COOKIE[AUTH_COOKIE] = wp_generate_auth_cookie($userID, 2 * DAY_IN_SECONDS);
Run Code Online (Sandbox Code Playgroud)

..这一个:

$_set_cookies = true; // for the closures

// Set the (secure) auth cookie immediately. We need only the first and last
// arguments; hence I renamed the other three, namely `$a`, `$b`, and `$c`.
add_action( 'set_auth_cookie', function( $auth_cookie, $a, $b, $c, $scheme ) use( $_set_cookies ){
    if ( $_set_cookies ) {
        $_COOKIE[ 'secure_auth' === $scheme ? SECURE_AUTH_COOKIE : AUTH_COOKIE ] = $auth_cookie;
    }
}, 10, 5 );

// Set the logged-in cookie immediately. `wp_create_nonce()` relies upon this
// cookie; hence, we must also set it.
add_action( 'set_logged_in_cookie', function( $logged_in_cookie ) use( $_set_cookies ){
    if ( $_set_cookies ) {
        $_COOKIE[ LOGGED_IN_COOKIE ] = $logged_in_cookie;
    }
} );

// Set cookies.
wp_set_auth_cookie($userID);
$_set_cookies = false;
Run Code Online (Sandbox Code Playgroud)

工作示例(在WordPress 4.9.5上测试)

PHP/WP REST API

function myplugin__change_password( $password, $userID ) {
    //$userID = get_current_user_id();
    wp_set_password($password, $userID);

    // Log user in.
    wp_set_current_user($userID);
    $_set_cookies = true; // for the closures

    // Set the (secure) auth cookie immediately. We need only the first and last
    // arguments; hence I renamed the other three, namely `$a`, `$b`, and `$c`.
    add_action( 'set_auth_cookie', function( $auth_cookie, $a, $b, $c, $scheme ) use( $_set_cookies ){
        if ( $_set_cookies ) {
            $_COOKIE[ 'secure_auth' === $scheme ? SECURE_AUTH_COOKIE : AUTH_COOKIE ] = $auth_cookie;
        }
    }, 10, 5 );

    // Set the logged-in cookie immediately. `wp_create_nonce()` relies upon this
    // cookie; hence, we must also set it.
    add_action( 'set_logged_in_cookie', function( $logged_in_cookie ) use( $_set_cookies ){
        if ( $_set_cookies ) {
            $_COOKIE[ LOGGED_IN_COOKIE ] = $logged_in_cookie;
        }
    } );

    // Set cookies.
    wp_set_auth_cookie($userID);
    $_set_cookies = false;

    //Return fresh nonce
    return new WP_Rest_Response(array(
        'nonce'  => wp_create_nonce('wp_rest'),
        'status' => 'password_changed',
    ));
}

function myplugin_change_password( WP_REST_Request $request ) {
    $old_pwd = $request->get_param( 'old_pwd' );
    $new_pwd = $request->get_param( 'new_pwd' );

    $user = wp_get_current_user();
    if ( ! wp_check_password( $old_pwd, $user->user_pass, $user->ID ) ) {
        return new WP_Error( 'wrong_password', 'Old password incorrect' );
    }

    if ( $old_pwd !== $new_pwd ) {
        return myplugin__change_password( $new_pwd, $user->ID );
    }

    return new WP_Rest_Response( [
        'nonce'  => wp_create_nonce( 'wp_rest' ),
        'status' => 'passwords_equal',
    ], 200 );
}

add_action( 'rest_api_init', function(){
    register_rest_route( 'myplugin/v1', '/change-password', [
        'methods'             => 'POST',
        'callback'            => 'myplugin_change_password',
        'args'                => [
            'old_pwd' => [
                'type'              => 'string',
                'validate_callback' => function( $param ) {
                    return ! empty( $param );
                }
            ],
            'new_pwd' => [
                'type'              => 'string',
                'validate_callback' => function( $param ) {
                    return ! empty( $param );
                }
            ],
        ],
        'permission_callback' => function(){
            // Only logged-in users.
            return current_user_can( 'read' );
        },
    ] );
} );
Run Code Online (Sandbox Code Playgroud)

HTML /表格

<fieldset>
    <legend>Change Password</legend>

    <p>
        To change your password, please enter your old or current password.
    </p>

    <label>
        Old Password:
        <input id="old_passwd">
    </label>
    <label>
        New Password:
        <input id="new_passwd">
    </label>

    <label>
        Old nonce: (read-only)
        <input id="old_nonce" value="<?= wp_create_nonce( 'wp_rest' ) ?>"
        readonly disabled>
    </label>
    <label>
        New nonce: (read-only)
        <input id="new_nonce" readonly disabled>
    </label>

    <button id="go" onclick="change_passwd()">Change</button>
</fieldset>
<div id="rest-res"><!-- AJAX response goes here --></div>
Run Code Online (Sandbox Code Playgroud)

jQuery/AJAX

function change_passwd() {
    var apiurl = '/wp-json/myplugin/v1/change-password',
        $ = jQuery;

    $.post( apiurl, {
        old_pwd: $( '#old_passwd' ).val(),
        new_pwd: $( '#new_passwd' ).val(),
        _wpnonce: $( '#new_nonce' ).val() || $( '#old_nonce' ).val()
    }, function( res ){
        $( '#new_nonce' ).val( res.nonce );

        // Update the global nonce for scripts using the `wp-api` script.
        if ( 'object' === typeof wpApiSettings ) {
            wpApiSettings.nonce = res.nonce;
        }

        $( '#rest-res' ).html( '<b>Password changed successfully.</b>' );
    }, 'json' ).fail( function( xhr ){
        try {
            var res = JSON.parse( xhr.responseText );
        } catch ( err ) {
            return;
        }

        if ( res.code ) {
            $( '#rest-res' ).html( '<b>[ERROR]</b> ' + res.message );
        }
    });
}
Run Code Online (Sandbox Code Playgroud)