Как ограничить доступ к WP REST API для внешних запросов в WordPress

WP REST API часто оставляют открытым «как есть», а потом удивляются лишним запросам к /wp-json/, утечке служебных данных и шуму в логах. Полностью выключать REST API обычно нельзя: на нём завязаны блоковый редактор, некоторые плагины, формы, интеграции и фронтенд-скрипты. Рабочая задача здесь другая — убрать лишнее и оставить только то, что действительно нужно сайту.

Ниже — практический сценарий: как понять, что именно торчит наружу, как ограничить доступ по ролям, по маршрутам и по источнику запроса, а потом проверить, что сайт не сломался.

Когда REST API нужно ограничивать, а не отключать

Если сайт обычный контентный, REST API нередко используется только редактором и внутренними запросами. В таком случае нет смысла держать открытыми все публичные маршруты, особенно если в ответах видны авторы, типы записей, таксономии и метаданные, которые не нужны посетителю.

Ограничение особенно полезно, если вы видите:

  • много запросов к /wp-json/wp/v2/ в логах от ботов;
  • сканирование пользователей через REST-эндпоинты;
  • публичные интеграции, которые должны работать только для авторизованных запросов;
  • конфликты с кэшем или WAF, когда API дергают снаружи без необходимости.

Диагностика: что именно открыто сейчас

Сначала проверьте, какие маршруты доступны без авторизации. Самый простой способ — открыть /wp-json/ в браузере и посмотреть, какие namespace и endpoints отдаются. Для точечной проверки используйте curl:

curl -i https://example.com/wp-json/wp/v2/posts

Если в ответе приходит JSON со списком записей без авторизации, это нормально для публичного контента. Но если вы видите лишние поля, служебные маршруты или данные, которые не должны быть доступны всем, их нужно ограничить на уровне кода или правил доступа.

Дополнительно проверьте, какие плагины уже используют REST API. Иногда проблема не в WordPress, а в стороннем плагине, который ожидает открытый маршрут для AJAX-логики или внешней синхронизации.

Что лучше: плагин, код или серверное правило

ПодходКогда подходитМинус
Плагин безопасностиНужно быстро закрыть часть маршрутов без разработкиМожет конфликтовать с редактором или интеграциями
Код в теме или MU-плагинеНужен точный контроль над маршрутами и ролямиТребует тестирования после обновлений
Правила на сервере / WAFНужно отрезать внешние запросы до PHPЛегко сломать легитимные запросы, если фильтр слишком грубый

Для большинства сайтов лучше начинать с кода: так вы ограничите только нужные маршруты и не затронете всё подряд.

Пошаговое решение: ограничиваем REST API по маршрутам

Если задача — запретить внешним пользователям доступ к части API, но оставить редактор и авторизованные запросы, удобно использовать фильтр rest_authentication_errors. Он срабатывает до выполнения маршрута и позволяет вернуть ошибку для неавторизованных запросов.

add_filter( 'rest_authentication_errors', function( $result ) {
    if ( ! empty( $result ) ) {
        return $result;
    }

    // Разрешаем доступ авторизованным пользователям.
    if ( is_user_logged_in() ) {
        return $result;
    }

    $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';

    // Оставляем публичные маршруты, если они реально нужны.
    $allowed_prefixes = array(
        '/wp-json/wp/v2/posts',
        '/wp-json/wp/v2/pages',
    );

    foreach ( $allowed_prefixes as $prefix ) {
        if ( strpos( $request_uri, $prefix ) !== false ) {
            return $result;
        }
    }

    return new WP_Error(
        'rest_forbidden',
        __( 'REST API доступен только для авторизованных запросов.', 'textdomain' ),
        array( 'status' => 403 )
    );
} );

Этот вариант грубый, но понятный. Он полезен, когда нужно быстро закрыть большую часть API и оставить только несколько публичных маршрутов. Если у вас есть фронтенд-форма, SPA или внешний сервис, список разрешений надо расширять точечно.

Более аккуратный вариант: блокируем только конкретные namespace

Если вы знаете, что наружу не должны уходить, например, пользовательские маршруты или служебные endpoints плагина, лучше проверять сам REST-запрос, а не URI целиком. Для этого подходит фильтр rest_pre_dispatch или проверка маршрута через объект запроса.

add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) {
    if ( is_user_logged_in() ) {
        return $result;
    }

    $route = $request->get_route();

    // Закрываем только пользовательский namespace.
    if ( strpos( $route, '/myplugin/v1/' ) === 0 ) {
        return new WP_Error(
            'rest_forbidden',
            __( 'Этот API-маршрут закрыт для внешних запросов.', 'textdomain' ),
            array( 'status' => 403 )
        );
    }

    return $result;
}, 10, 3 );

Такой подход лучше, если вы сами регистрируете маршруты через register_rest_route() и хотите оставить публичными только отдельные эндпоинты.

Если нужно ограничить доступ по IP или по заголовку

Иногда REST API должен работать только для внутреннего сервера, CRM или приложения. Тогда можно добавить проверку по IP или по секретному заголовку. Это не замена полноценной авторизации, но для закрытой интеграции подходит.

add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) {
    $route = $request->get_route();

    if ( strpos( $route, '/myplugin/v1/webhook' ) !== 0 ) {
        return $result;
    }

    $secret = isset( $_SERVER['HTTP_X_API_SECRET'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_API_SECRET'] ) ) : '';
    $expected = defined( 'MYPLUGIN_API_SECRET' ) ? MYPLUGIN_API_SECRET : '';

    if ( empty( $expected ) || ! hash_equals( $expected, $secret ) ) {
        return new WP_Error( 'rest_forbidden', 'Invalid API secret.', array( 'status' => 403 ) );
    }

    return $result;
}, 10, 3 );

Секрет лучше хранить в wp-config.php, а не в теме. Если интеграция критичная, дополнительно ограничьте доступ на уровне сервера или через firewall.

Проверка результата после внедрения

После изменений не ограничивайтесь открытием главной страницы. Проверьте конкретные сценарии, которые реально завязаны на REST API:

  • открывается ли редактор записей в админке;
  • работают ли блоки, которые подгружают данные через API;
  • не сломались ли формы, которые отправляют данные в /wp-json/;
  • возвращают ли закрытые маршруты 403 для гостя;
  • проходит ли авторизованный запрос от администратора или нужной роли.

Для проверки удобно использовать curl и смотреть код ответа:

curl -I https://example.com/wp-json/myplugin/v1/webhook
curl -I -H 'X-API-SECRET: your-secret' https://example.com/wp-json/myplugin/v1/webhook

Если первый запрос даёт 403, а второй проходит, логика работает. Если редактор в админке начал сыпать ошибками, значит вы закрыли слишком широкий маршрут или не учли авторизованные запросы с nonce.

Частые ошибки и как их исправить

Слишком широкий запрет по URI

Ошибка типичная: в коде проверяют только наличие /wp-json/ и режут всё подряд. В результате ломаются публичные записи, редактор и плагины. Исправление простое: ограничивайте не весь API, а конкретные namespace или маршруты.

Игнорирование авторизованных запросов

Если не учитывать is_user_logged_in() или nonce, админка может перестать сохранять записи. Для внутренних запросов WordPress часто использует свои механизмы авторизации, и их нельзя рубить без проверки.

Код добавлен в тему, а тема потом обновилась

Если вы вносите ограничения в functions.php активной темы, при смене темы логика исчезнет. Для таких задач лучше использовать MU-плагин или отдельный мини-плагин.

Проверка только в браузере

В браузере всё может выглядеть нормально, но внешний бот или сервис получит другой ответ. Проверяйте именно HTTP-код и тело ответа через curl или Postman.

Безопасность и производительность: что учесть на практике

Ограничение REST API не должно превращаться в ручной список исключений, который никто не поддерживает. Если маршрутов много, заведите короткий документ: какие endpoints публичные, какие закрыты, кто их использует и зачем. Это экономит время при отладке после обновлений.

Если у вас есть плагин для очистки сайта и удаления дублей, например Clearfy Pro, его имеет смысл рассматривать как вспомогательный инструмент для технической гигиены, но не как замену точечной логике доступа. Для REST API всё равно лучше оставить контроль в коде или на сервере. Подробности можно посмотреть на странице Clearfy Pro, если вам нужен набор смежных функций по чистке и SEO-оптимизации.

И ещё один практический момент: если вы закрываете API для внешних запросов, проверьте кэширование. Иногда CDN или reverse proxy продолжает отдавать старый публичный ответ, и кажется, что ограничение не сработало. После изменений очистите кэш на всех уровнях: плагин, сервер, CDN.

Короткий чек-лист перед публикацией изменений

  • Проверен список маршрутов, которые должны остаться открытыми.
  • Добавлена авторизация или исключение для админки и редактора.
  • Проверка выполнена через curl, а не только в браузере.
  • Очистен кэш сайта, сервера и CDN.
  • Зафиксировано, какие плагины и интеграции используют REST API.
  • Код вынесен в MU-плагин или отдельный плагин, а не в активную тему.

Если после ограничения REST API сайт продолжает работать, а лишние маршруты возвращают 403, задача решена правильно: вы не отключили важный механизм WordPress, а сузили его до нужного набора запросов.

⭐⭐⭐⭐⭐