最早开发小半WordPress ai助手的时候并不是用的REST API服务,而是全程用的AJAX处理,包括以前的插件也都是AJAX处理,毕竟没啥大功能,但是ai助手不行。

特别是要支持流式输出,如果用AJAX处理,会中断卡死,最后就选择了REST API服务。

AJAX处理相对更适合小功能,不频繁处理以及后台的功能,AJAX每次处理的时候,会重载WordPress的核心环境,且无法做缓存,如果做了缓存功能可能就失效了,这对小型服务器来说,也是一种负担。

用REST API服务虽然每次请求都会重载整个网站,但是REST API的请求可以走缓存。

只是网站开启了REST API服务,就会暴露很多REST相关的页面,特别是用户页面users,这个算低危漏洞,毕竟暴露网站用户名了,如果你网站要做公安备案,可能会收到官方的漏洞通知,所以我们提前做下屏蔽。

所以用了REST API服务,就需要手动进行屏蔽,把一些不想暴露的页面给屏蔽了。

对比总结

方面 REST API Admin AJAX
安全性 内置权限控制,需手动屏蔽公开端点 手动验证,默认内部
速度 支持缓存,响应快 简单请求快,缓存不友好
性能 加载轻量,高并发友好 加载较重,高并发可能瓶颈
扩展性 优秀,适合现代架构 有限,适合小型功能
兼容性 WordPress 4.4+ 几乎所有版本
适用场景 前后端分离 后台功能

 

REST API服务安全防护

我几个网站功能不同,开放权限也都不一样,所以配置不同,看着有点懵圈,我自己配置的时候都混乱了。

1、如果你没有需要公开的数据,可以直接仅登录用户才能使用REST API服务。

把代码加到主题的functions.php文件里面:

add_filter('rest_authentication_errors', function($result) {
    if (!is_user_logged_in()) {
        return new WP_Error('function_disabled', 'This feature is not available.', array('status' => 401));
    }
    return $result;
});

2、如果你有需要公开的数据,可以屏蔽重点页面。

比如在用Gutenberg编辑器、小程序、app之类的。

屏蔽media、settings、users这3个页面以及动态?rest_route=media、settings、users对应的3个页面。

服务器规则来限制,nginx配置:

location ~ ^/wp-json/wp/v2/(media|settings|users) {
  deny all;
  return 403;
  }

不推荐在nginx规则里面用if,所以屏蔽动态页面的直接在php端来实现:

// 拦截 ?rest_route= 形式的请求
add_action('parse_request', function($wp) {
    // 仅处理包含 rest_route 参数的请求
    if (empty($_GET['rest_route'])) {
        return;
    }
    
    $rest_route = trim($_GET['rest_route'], '/');
    $blocked_routes = [
        'wp/v2/users',
        'wp/v2/users/(?P<id>[\\d]+)',
        'wp/v2/media',
        'wp/v2/settings'
    ];

    foreach ($blocked_routes as $route) {
        if (preg_match('#^' . str_replace(['\d+', '\w+'], ['[0-9]+', '[a-zA-Z0-9_-]+'], preg_quote($route, '#')) . '(/|$)#', $rest_route)) {
            status_header(403);
            wp_die('REST API 访问被禁止', 'Forbidden', ['response' => 403]);
            exit;
        }
    }
}, 1);

把这个放到主题的functions.php文件里面就行。

3、如果你需要公开指定页面端口,其他屏蔽。

比如只开放文章和分类页面,但是其他页面全部屏蔽

# 允许 /wp-json/wp/v2/posts 及其子路由和categories路由
location ~* ^/wp-json/wp/v2/(posts(/.*)?|categories)$ {
   try_files $uri $uri/ /index.php?$args;
}

# 屏蔽其他页面
location ~* ^/wp-json(/.*)?$ {
    deny all;
    return 403;
}

这是改nginx配置文件。

然后还是需要修改主题文件:

add_filter('rest_authentication_errors', function($result) {
    // 获取当前 REST 路由
    $rest_route = isset($_GET['rest_route']) ? $_GET['rest_route'] : '';
    if (empty($rest_route)) {
        // 处理 /wp-json/ 请求
        $rest_route = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $rest_route = str_replace('/wp-json', '', $rest_route);
    }

    // 允许公开访问 /wp/v2/posts 及其子路由
    if (strpos($rest_route, '/wp/v2/posts') === 0) {
        return $result;
    }
    // 允许公开访问 /wp/v2/categories(精确匹配)
    if ($rest_route === '/wp/v2/categories') {
        return $result;
    }

    // 其他端点要求登录
    if (!is_user_logged_in()) {
        return new WP_Error('function_disabled', 'This feature is not available.', array('status' => 401));
    }

    return $result;
});

把这个放到主题的functions.php文件里面。

4、屏蔽全部默认的v2页面数据,并且禁止直接访问 /wp-json 和 /wp-json/页面:

nginx配置:

# 禁止直接访问 /wp-json 和 /wp-json/
 location = /wp-json {
   return 301 $scheme://$host/;
 }
 location = /wp-json/ {
   return 301 $scheme://$host/;
 }
# 禁止直接访问 ?rest_route=/ 和 ?rest_route=
 location ~* \?rest_route=(/)?$ {
   return 301 $scheme://$host/;
 }
 # 屏蔽 WordPress 默认 REST API 端点(wp/v2 和 oembed/1.0)
 location ~* ^/wp-json/(wp/v2|oembed/1.0)(/.*)?$ {
   deny all;
   return 403;
 }

 # 其他 REST API 请求交给 WordPress 处理
 location ~* ^/wp-json(/.*)?$ {
   try_files $uri $uri/ /index.php?$args;
 }

动态处理:

// 专门拦截 ?rest_route= 形式的请求
add_action('parse_request', function($wp) {
    if (empty($_GET['rest_route'])) {
        return;
    }
    $rest_route = trim($_GET['rest_route'], '/');
    if (preg_match('#^(wp/v2|oembed/1.0)(/.*)?$#', $rest_route)) {
        header('Content-Type: application/json');
        status_header(403);
        echo json_encode([
            'code' => 'function_disabled',
            'message' => 'REST API 访问被禁止',
            'data' => ['status' => 403]
        ]);
        exit;
    }
}, 1);

放functions.php文件里面。

nginx是管/wp-json/页面的,WordPress的php代码,也就是functions.php文件是管带问号的$rest_route这种动态页面。

根据你自己的需求来,如果不用就直接关闭这个功能。

直接关闭:

add_filter('rest_enabled', '__return_false');
add_filter('rest_jsonp_enabled', '__return_false');

这个也是可以放到functions.php文件里面。

现在很多主题或者插件都自带关闭REST API服务的功能。

我现在的方案:

1、未登录的用户只开放文章和分类页面,其他一律屏蔽。

wordpress层级代码:

// 未登录开放指定页面
add_filter('rest_authentication_errors', function($result) {
    // 获取当前 REST 路由
    $rest_route = isset($_GET['rest_route']) ? $_GET['rest_route'] : '';
    if (empty($rest_route)) {
        // 处理 /wp-json/ 请求
        $rest_route = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $rest_route = str_replace('/wp-json', '', $rest_route);
    }

    // 允许公开访问 /wp/v2/posts 及其子路由
    if (strpos($rest_route, '/wp/v2/posts') === 0) {
        return $result;
    }
    // 允许公开访问 /wp/v2/categories(精确匹配)
    if ($rest_route === '/wp/v2/categories') {
        return $result;
    }

    // 其他端点要求登录
    if (!is_user_logged_in()) {
        return new WP_Error('function_disabled', 'This feature is not available.', array('status' => 401));
    }

    return $result;
});

nginx配置:

# 允许 /wp-json/wp/v2/posts 及其子路由
location ~* ^/wp-json/wp/v2/posts(/.*)?$ {
  try_files $uri $uri/ /index.php?$args;
}

# 只允许 /wp-json/wp/v2/categories
location = /wp-json/wp/v2/categories {
  try_files $uri $uri/ /index.php$is_args$args;
}

location ~* ^/wp-json(/.*)?$ {
    deny all;
    return 403;
}

这个表示:

  1. 只可以正常获取到文章内容和文章分类
  2. 其他页面一律不能访问,直接403提示。

这个网站是企业官网,文章和分类是放到首页的,给用户看最新动态啥的,其他都用不到了,这个首页是一个独立的页面,所以才用了这种方式。

 

2、默认屏蔽所有自带页面数据(/wp/v2),对未登录用户开放指定端口,剩余端口需要登录访问。

wordpress层级代码:

// 未登录访问
add_filter('rest_authentication_errors', function($result) {
    $current_route = untrailingslashit($GLOBALS['wp']->query_vars['rest_route']);
    $allowed_routes = [
        '/自定义端口/v1',
    ];
    $allowed_patterns = [
        '#^/自定义端口/v1/convert/[^/]+$#',
    ];
    if (in_array($current_route, $allowed_routes)) {
        return $result;
    }
    foreach ($allowed_patterns as $pattern) {
        if (preg_match($pattern, $current_route)) {
            return $result;
        }
    }
    if (!is_user_logged_in()) {
        return new WP_Error('function_disabled', 'This feature is not available.', array('status' => 401));
    }
    return $result;
});

// 拦截自带 ?rest_route= 形式的请求
add_action('parse_request', function($wp) {
    if (empty($_GET['rest_route'])) {
        return;
    }
    $rest_route = trim($_GET['rest_route'], '/');
    if (preg_match('#^(wp/v2|oembed/1.0)(/.*)?$#', $rest_route)) {
        header('Content-Type: application/json');
        status_header(403);
        echo json_encode([
            'code' => 'function_disabled',
            'message' => 'This feature is not available.',
            'data' => ['status' => 403]
        ]);
        exit;
    }
}, 1);

nginx配置:

# 禁止直接访问 /wp-json 和 /wp-json/
location = /wp-json {
  return 301 $scheme://$host/;
}
location = /wp-json/ {
  return 301 $scheme://$host/;
}
# 禁止 ?rest_route=/ 和 ?rest_route=
location ~* \?rest_route=(/)?$ {
  return 301 $scheme://$host/;
}
# 允许特定的插件 REST API 端点
# location ~* ^/wp-json/(自定义端口1/v1(/.*)?|自定义端口2/v1(/.*)?)$ {
#      try_files $uri $uri/ /index.php?$args;
#  }
#允许特定端口这段可以不用
# 屏蔽 WordPress 默认 REST API 端点(wp/v2 和 oembed/1.0)
location ~* ^/wp-json/(wp/v2|oembed/1.0)(/.*)?$ {
  deny all;
  return 403;
}

# 其他 REST API 请求交给 WordPress 处理
location ~* ^/wp-json(/.*)?$ {
  try_files $uri $uri/ /index.php?$args;
}

这个表示:

  1. 屏蔽默认的wp/v2|oembed/页面,就算已经登录的用户也不能访问。
  2. 允许没有登录的用户使用自定义的端口请求
  3. 其他自定义端口都需要登录才能访问
  4. 禁止直接访问/wp-json、 /wp-json/、?rest_route=/ 和 ?rest_route=页面

这个网站是一个功能服务站点,部分功能对游客进行了开放。

3、禁止所有未登录用户访问,已登录用户也不能访问默认v2端口

WordPress层级:

add_filter('rest_authentication_errors', function($result) {
    if (!is_user_logged_in()) {
        return new WP_Error('function_disabled', 'This feature is not available.', array('status' => 401));
    }
    return $result;
});

nginx配置:

# 禁止直接访问 /wp-json 和 /wp-json/
location = /wp-json {
 return 301 $scheme://$host/;
}
 location = /wp-json/ {
 return 301 $scheme://$host/;
}
 # 禁止 ?rest_route=/ 和 ?rest_route=
location ~* \?rest_route=(/)?$ {
 return 301 $scheme://$host/;
}
# 屏蔽 WordPress 默认 REST API 端点(wp/v2 和 oembed/1.0)
location ~* ^/wp-json/(wp/v2|oembed/1.0)(/.*)?$ {
  deny all;
  return 403;
}

这个就表示:

  1. 禁止未登录的用户访问?rest_route相关页面以及其他自定义端口
  2. 禁止所有用户访问默认的wp/v2 和 oembed/1.0端口
  3. 已登录用户可以访问自定义端口
  4. 禁止直接访问/wp-json、/wp-json/、?rest_route=/ 和 ?rest_route=

这个网站是有一些自定义的插件,需要开放,但是其他默认的都不需要开放。

 

4、未登录的用户开放文章和分类页面,登录用户访问指定端口,其他一律屏蔽。

WordPress层级代码:

// 控制 REST API 访问
add_filter('rest_authentication_errors', function($result) {
    // 获取标准化的 REST 路由
    $current_route = untrailingslashit($GLOBALS['wp']->query_vars['rest_route'] ?? '');

    // 允许未登录用户访问的端点
    $allowed_routes = [
        '/wp/v2/posts',
        '/wp/v2/categories',
    ];
    $allowed_patterns = [
        '#^/wp/v2/posts/[^/]+$#' // 支持 /wp/v2/posts/{id}
    ];

    // 检查是否公开访问
    if (in_array($current_route, $allowed_routes) || preg_match($allowed_patterns[0], $current_route)) {
        return $result;
    }

    // 其他端点要求登录
    if (!is_user_logged_in()) {
        return new WP_Error('function_disabled', 'This feature is not available.', array('status' => 401));
    }

    return $result;
});

// 拦截 ?rest_route= 形式的请求
add_action('parse_request', function($wp) {
    if (empty($_GET['rest_route'])) {
        return;
    }
    $rest_route = trim($_GET['rest_route'], '/');
    
    // 允许公开访问的路由
    $allowed_routes = [
        'wp/v2/posts',
        'wp/v2/categories',
    ];
    $allowed_patterns = [
        '#^wp/v2/posts/[^/]+$#' // 支持 ?rest_route=/wp/v2/posts/{id}
    ];

    // 检查是否公开访问
    if (in_array($rest_route, $allowed_routes) || preg_match($allowed_patterns[0], $rest_route)) {
        return;
    }

    // 屏蔽默认端点和其他端点
    if ($rest_route === '' || preg_match('#^(wp/v2|oembed/1.0)(/.*)?$#', $rest_route)) {
        header('Content-Type: application/json');
        status_header(403);
        echo json_encode([
            'code' => 'function_disabled',
            'message' => 'This feature is not available.',
            'data' => ['status' => 403]
        ]);
        exit;
    }

    // 其他端点要求登录
    if (!is_user_logged_in()) {
        header('Content-Type: application/json');
        status_header(401);
        echo json_encode([
            'code' => 'function_disabled',
            'message' => 'This feature is not available.',
            'data' => ['status' => 401]
        ]);
        exit;
    }
}, 1);

nginx配置:

# 禁止直接访问 /wp-json 和 /wp-json/
location = /wp-json {
  return 301 $scheme://$host/;
}
location = /wp-json/ {
  return 301 $scheme://$host/;
}

# 禁止 ?rest_route=/ 和 ?rest_route=
location ~* \?rest_route=(/)?$ {
  return 301 $scheme://$host/;
}

# 允许公开访问的端点
location ~* ^/wp-json/wp/v2/(posts(/.*)?|categories)$ {
  try_files $uri $uri/ /index.php?$args;
}

# 允许自定义端点(需登录)
location ~* ^/wp-json/deepseek/v1(/.*)?$ {
  try_files $uri $uri/ /index.php?$args;
}

location ~* ^/wp-json(/.*)?$ {
    default_type application/json;
    return 403;
}

这个表示:

  1. 文章和分类是公开的,游客也可以访问
  2. 其中自定义端口deepseek/v1允许登录访问
  3. 并且禁止直接访问/wp-json 、 /wp-json/、?rest_route=/ 和 ?rest_route=
  4. 其他的一律禁止,403错误。

 

根据自己的需求,问清楚ai怎么写,让它写好全部规则就行。