在register_rest_route中结合JWT正确使用permission_callback的问题
WordPress插件REST API权限回调审核不通过的问题分析与修复
问题根源
你的插件被WordPress.org拒绝,核心是以下不符合审核规范的问题:
1. 敏感接口设置了完全公开的权限回调
change-password是修改用户密码的敏感接口,但你的public_permission函数直接返回true,意味着任何未认证的请求都能调用该接口修改任意用户密码,这是严重的安全漏洞。
2. 自定义权限回调未遵循WP REST API规范
- 直接使用
getallheaders()获取请求头,该函数在部分服务器环境下兼容性差,WP REST API要求通过权限回调的$request参数(WP_REST_Request对象)获取请求信息。 - JWT校验通过后仅设置了当前用户,但未验证该用户是否具备执行对应操作的权限(比如删除购物车优惠券需要用户拥有操作自身购物车的权限),不符合WP权限系统的设计要求。
- 权限校验失败时仅返回
false,没有返回标准的WP_Error对象,无法向客户端返回清晰的错误信息。
3. 冗余的自定义公开权限函数
WP已经内置了__return_true函数用于完全公开的接口,无需自定义public_permission函数,使用内置函数更符合官方规范。
修复方案
1. 修复修改密码接口的权限校验
修改密码接口绝对不能完全公开,必须限制为已登录用户修改自身密码:
// 修改密码路由(仅允许已登录用户修改自己的密码) register_rest_route(MYPLUG_API_V1, 'change-password', [ 'methods' => 'POST', 'callback' => [new MyPlug_Api_Auth(), 'changePassword'], 'permission_callback' => function(WP_REST_Request $request) { // 检查用户是否登录 if (!is_user_logged_in()) { return new WP_Error( 'rest_not_logged_in', __('用户未登录', 'myplug'), ['status' => 401] ); } // 确保只能修改自己的密码 $current_user_id = get_current_user_id(); $target_user_id = isset($request['user_id']) ? (int)$request['user_id'] : $current_user_id; if ($target_user_id !== $current_user_id) { return new WP_Error( 'rest_forbidden', __('无权限修改该用户密码', 'myplug'), ['status' => 403] ); } return true; }, ]);
2. 重构受保护路由的权限校验函数
修改myplug_check_auth函数,遵循WP REST API规范,增加权限验证并返回标准错误:
function myplug_check_auth(WP_REST_Request $request): bool|WP_Error { // 通过WP_REST_Request对象获取Authorization头 $auth_header = $request->get_header('Authorization'); if (empty($auth_header)) { return new WP_Error( 'rest_no_auth_header', __('缺少Authorization请求头', 'myplug'), ['status' => 401] ); } if (!preg_match('/Bearer\s(\S+)/', $auth_header, $matches)) { return new WP_Error( 'rest_invalid_auth_format', __('Authorization头格式错误,应为Bearer <token>', 'myplug'), ['status' => 401] ); } $token = $matches[1]; $secret_key = defined('MYPLUG_SECRET_KEY') ? MYPLUG_SECRET_KEY : false; if (!$secret_key) { return new WP_Error( 'rest_missing_secret', __('缺少JWT密钥配置', 'myplug'), ['status' => 500] ); } $alg = 'HS256'; try { $decoded = JWT::decode($token, new Key($secret_key, $alg)); // 检查令牌是否过期 if ($decoded->exp < time()) { return new WP_Error( 'rest_token_expired', __('令牌已过期', 'myplug'), ['status' => 401] ); } $user_id = $decoded->data->user->id; // 验证用户是否存在 if (!user_exists($user_id)) { return new WP_Error( 'rest_invalid_user', __('用户不存在', 'myplug'), ['status' => 401] ); } wp_set_current_user($user_id); // 验证用户是否具备操作购物车优惠券的权限 // 示例:如果是操作自身购物车,可校验购物车所属用户ID与当前用户ID一致 // 这里假设使用自定义权限capability:manage_cart_coupons if (!current_user_can('manage_cart_coupons')) { return new WP_Error( 'rest_forbidden', __('无权限操作购物车优惠券', 'myplug'), ['status' => 403] ); } return true; } catch (Exception $e) { return new WP_Error( 'rest_invalid_token', __('无效令牌:' . $e->getMessage(), 'myplug'), ['status' => 401] ); } }
注册受保护路由时无需修改,回调会自动接收$request参数:
// 受保护路由 register_rest_route(MYPLUG_API_V1, '/cart/coupons', [ 'methods' => 'GET', 'callback' => [new MyPlug_Api_Controller(), 'deleteAllCartCoupons'], 'permission_callback' => 'myplug_check_auth', ]);
3. 规范完全公开接口的权限回调
如果你的插件有真正无需认证的公开接口(比如获取公开商品列表),直接使用WP内置的__return_true:
register_rest_route(MYPLUG_API_V1, 'public/products', [ 'methods' => 'GET', 'callback' => [new MyPlug_Api_Public(), 'getProducts'], 'permission_callback' => '__return_true', ]);
审核关键要点
- 所有接口的权限回调必须与功能匹配,敏感操作绝对不能完全公开
- 权限校验失败时必须返回
WP_Error对象,包含明确的错误信息和正确的HTTP状态码 - 优先使用WP内置的权限函数(
is_user_logged_in()、current_user_can()等),自定义校验需兼容WP权限系统 - 处理请求时依赖
WP_REST_Request对象,避免使用服务器环境相关的全局函数
内容的提问来源于stack exchange,提问作者Moniruzzaman Saikat
相关产品推荐
相关产品推荐

