最早开发小半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; }
这个表示:
- 只可以正常获取到文章内容和文章分类
- 其他页面一律不能访问,直接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; }
这个表示:
- 屏蔽默认的wp/v2|oembed/页面,就算已经登录的用户也不能访问。
- 允许没有登录的用户使用自定义的端口请求
- 其他自定义端口都需要登录才能访问
- 禁止直接访问/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; }
这个就表示:
- 禁止未登录的用户访问?rest_route相关页面以及其他自定义端口
- 禁止所有用户访问默认的wp/v2 和 oembed/1.0端口
- 已登录用户可以访问自定义端口
- 禁止直接访问/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; }
这个表示:
- 文章和分类是公开的,游客也可以访问
- 其中自定义端口deepseek/v1允许登录访问
- 并且禁止直接访问/wp-json 、 /wp-json/、?rest_route=/ 和 ?rest_route=
- 其他的一律禁止,403错误。
根据自己的需求,问清楚ai怎么写,让它写好全部规则就行。