WordPress wp_signon() 函数在SSL连接下无法登录后台

周五上线前几个小时,QA测试出图片无法上传的BUG。经更多测试发现所有使用Cookie进行登陆验证的API都会重定向到登陆界面,比如/wp-admin/async-upload.php包括后台管理页面/wp-admin/index.php。诡异的地方在于,本地测试环境没有问题,同样的代码到线上就不行。

之前用的API大多是wpnonce,没有遇到问题。通过wp-login.php进行登陆,无论线上线下也没有问题。问题可能出在我们自己写的登录插件调用的登陆函数。

插件调用了wp_signon函数。这个函数的第二个参数描述的很含糊。

$secure_cookie
(string|bool) (Optional) Whether to use secure cookie.

Default value: ''

Secure cookies的意思是仅通过加密的SSL连接传递Cookie的字段,而我们的线上是全程SSL,包括登陆的页面也是加密连接,可以看到Cookie确实通过Request Headers传递到服务器。

对比wp-login.php和我们的登陆插件函数,发现我们在$secure_cookie参数上简单粗暴地设置为false。而wp-login.php则经过了一系列逻辑判断,判断是不是通过SSL连接登陆,有没有设置为强制要求SSL连接登录。

对比不同的登陆方法返回的Cookie,里面的Security字段也不一致。或许WordPress只允许非Security的Cookie通过未加密的HTTP连接登陆,反过来讲,Security的Cookie才允许在SSL连接中登陆。我们调用的wp_signon函数无论是否是SSL连接都使用非Security的Cookie是无法在SSL连接中通过验证的,尽管登陆的时候是SSL连接,WordPress可能为两种类型的Cookie设置了不同的的Key。

以下是wp-signon的部分代码。

if ( '' === $secure_cookie )
    $secure_cookie = is_ssl();

/**
 * Filters whether to use a secure sign-on cookie.
 *
 * @since 3.1.0
 *
 * @param bool  $secure_cookie Whether to use a secure sign-on cookie.
 * @param array $credentials {
 *     Array of entered sign-on data.
 *
 *     @type string $user_login    Username.
 *     @type string $user_password Password entered.
 *     @type bool   $remember      Whether to 'remember' the user. Increases the time
 *                                 that the cookie will be kept. Default false.
 * }
 */
$secure_cookie = apply_filters( 'secure_signon_cookie', $secure_cookie, $credentials );

global $auth_secure_cookie; // XXX ugly hack to pass this to wp_authenticate_cookie
$auth_secure_cookie = $secure_cookie;

add_filter('authenticate', 'wp_authenticate_cookie', 30, 3);

$user = wp_authenticate($credentials['user_login'], $credentials['user_password']);

if ( is_wp_error($user) ) {
    if ( $user->get_error_codes() == array('empty_username', 'empty_password') ) {
        $user = new WP_Error('', '');
    }

    return $user;
}

wp_set_auth_cookie($user->ID, $credentials['remember'], $secure_cookie);

可以看到,$secure_cookie默认为'',会判断当前是否通过SSL连接登陆,这个参数是Optional可以不写的,这样就由WordPress管理Cookie的安全性。

随手写的一个false导致了一个历时几小时查找的Bug,其实仔细看官方文档下方的留言,同样有人遇到同样的问题#user-contributed-notes。而我的建议是忽略$secure_cookie这个参数。

原文链接:https://marskid.net/2018/05/20/wordpress-wp_signon-can-not-signon-with-ssl-enabled/