블로그

안전한 WordPress REST API 개발: 실용 가이드

WordPress에서 적절한 보안, 검증 및 성능 모범 사례를 적용하여 커스텀 REST API 엔드포인트를 구축하는 방법을 알아보세요. 이 가이드는 경로 등록, 입력 정제, 권한 콜백, nonce 사용 및 캐싱 전략을 다루어 API를 강력하고 효율적으로 만듭니다.

요약

WordPress REST API는 사이트 기능을 확장하는 강력한 도구이지만, 잘못 구현된 엔드포인트는 사이트를 보안 취약점에 노출시킬 수 있습니다. 이 글은 안전하지 않은 커스텀 API 경로의 일반적인 문제를 해결하기 위해 안전한 엔드포인트 구축 방법을 단계별로 안내합니다. 경로를 올바르게 등록하고, 사용자 입력을 검증 및 정제하며, 콜백을 통해 권한을 적용하고, nonce를 사용하여 CSRF를 방지하는 방법을 배우게 됩니다. 또한 캐싱 및 WP_Query 인수 사용과 같은 성능 최적화 기법도 다룹니다. 이러한 방법을 따르면 안전하고 성능이 뛰어난 REST 엔드포인트를 만들 수 있습니다. 실제 사례와 주의사항이 포함되어 있어 일반적인 함정을 피하는 데 도움이 됩니다.

WordPress REST API는 헤드리스 프론트엔드 구동부터 커스텀 통합까지 개발자에게 다양한 가능성을 열어줍니다. 그러나 큰 힘에는 큰 책임이 따릅니다. 모든 커스텀 엔드포인트는 제대로 보호되지 않으면 공격의 진입점이 될 수 있습니다. 이 가이드는 등록, 입력 처리, 권한, nonce 및 성능에 초점을 맞춰 WordPress에서 안전한 REST API 엔드포인트를 구축하는 방법을 안내합니다. 완료되면 안전하고 효율적이며 유지 관리가 가능한 엔드포인트를 만드는 반복 가능한 프로세스를 갖게 됩니다.

경로 등록: 기초

모든 REST 엔드포인트는 register_rest_route()로 시작합니다. 그러나 많은 개발자가 보안을 강화하는 중요한 매개변수를 생략합니다. 경로를 등록할 때는 네임스페이스(일반적으로 플러그인/테마 슬러그), 경로, 그리고 콜백, permission_callback 및 검증을 위한 args를 포함한 옵션 배열을 지정해야 합니다. 다른 플러그인과의 충돌을 피하기 위해 항상 고유한 네임스페이스를 사용하세요. 예:

add_action('rest_api_init', function () {
  register_rest_route('myplugin/v1', '/secure-data', array(
    'methods'             => 'GET',
    'callback'            => 'myplugin_secure_data_callback',
    'permission_callback' => 'myplugin_permission_check',
    'args'                => array(
      'user_id' => array(
        'required'          => true,
        'validate_callback' => function($param) { return is_numeric($param); },
        'sanitize_callback' => 'absint',
      ),
    ),
  ));
});

args 배열을 주목하세요. 이곳에서 검증 및 정제 규칙을 정의합니다. 콜백으로 예상 매개변수를 명시적으로 선언하면 잘못된 데이터가 메인 콜백에 도달하는 것을 방지할 수 있습니다. 이는 Mastering WordPress Hooks의 핵심 원칙입니다(단, REST에 적용됨). 간단한 엔드포인트라도 항상 args를 정의하여 예상 입력을 문서화하고 조기에 오타를 잡아내세요.

입력 검증 및 정제: 데이터 잠금

입력 처리는 가장 흔한 취약점의 원인입니다. WordPress는 검증(데이터가 기준을 충족하는지?)과 정제(데이터 정리)의 두 계층을 제공합니다. validate_callback을 사용하여 처리 전에 잘못된 입력을 거부하세요. 예를 들어 양의 정수만 허용하려면:

'validate_callback' => function($param) { return is_numeric($param) && $param > 0; }

그런 다음 sanitize_callback을 사용하여 값을 정제합니다(예: absint, sanitize_text_field, sanitize_email). HTML이 필요하지 않은 경우 wp_kses_post 사용을 피하고 더 엄격한 정제기를 선호하세요. 배열 매개변수의 경우 array_map('sanitize_text_field', $param)으로 각 항목을 매핑하세요.

모든 인수를 미리 정의하고 싶지 않다면 콜백 내에서 정제할 수도 있습니다:

$user_id = isset($request['user_id']) ? absint($request['user_id']) : 0;

하지만 이 방법은 문서화가 덜 됩니다. 복잡한 엔드포인트의 경우 인라인 검증 콜백을 선호하세요.

권한 콜백: 누가 접근할 수 있는가?

모든 엔드포인트에는 permission_callback이 있어야 합니다. 없으면 WordPress는 여전히 콜백을 요구하지만, 이전 버전에서는 __return_true가 기본값이어서 보안에 매우 취약합니다. 항상 불리언 또는 WP_Error 객체를 반환하세요. 일반적인 패턴:

function myplugin_permission_check() {
  if (!current_user_can('edit_posts')) {
    return new WP_Error('rest_forbidden', '이 리소스에 접근할 수 없습니다.', array('status' => 403));
  }
  return true;
}

역할 기반 접근의 경우 current_user_can()manage_options, edit_others_posts 또는 커스텀 기능과 같은 능력을 사용하세요. 역할 이름(예: 'administrator')을 하드코딩하지 말고 기능을 사용하여 사이트 소유자가 플러그인을 통해 조정할 수 있도록 하세요. 공개 엔드포인트(예: 게시된 글 가져오기)의 경우 정말 필요한 경우에만 'permission_callback' => '__return_true'를 설정하고, 항상 콜백에서 적절한 데이터 제한을 함께 사용하세요.

Nonce를 사용한 CSRF 보호

REST API 요청은 쿠키 인증 사용자에 대해 유효한 nonce를 확인하지만, 엔드포인트는 쿠키를 사용하지 않는 외부 클라이언트(예: 모바일 앱)에서 접근할 수 있습니다. 엔드포인트가 데이터를 수정하는 경우 CSRF(교차 사이트 요청 위조)로부터 보호되어야 합니다. 내부 사용의 경우 wp_rest nonce를 통해 nonce를 보내며(REST API 미들웨어가 자동 처리), 외부 엔드포인트의 경우 토큰 기반 인증을 구현하거나 요청 헤더에 WordPress nonce를 사용할 수 있습니다. JavaScript 예:

wp.apiFetch({ path: '/myplugin/v1/add-post', method: 'POST', data: { title: 'New' } });

이 방법은 내장 nonce를 사용합니다. 커스텀 통합을 구축하는 경우 wp_create_nonce('wp_rest')로 nonce를 생성하고 X-WP-Nonce 헤더에 포함하세요.

성능: 캐싱 및 최적화

안전한 엔드포인트도 느릴 수 있습니다. 트랜션트를 사용하여 비용이 많이 드는 쿼리를 캐싱하세요:

$cache_key = 'myplugin_recent_posts_' . $user_id;
$posts = get_transient($cache_key);
if (false === $posts) {
  $posts = get_posts(array('author' => $user_id, 'posts_per_page' => 10));
  set_transient($cache_key, $posts, HOUR_IN_SECONDS);
}

데이터가 변하는 경우 사용자별로 캐싱하세요. 또한 $request->get_param('page')offset을 사용하여 페이지네이션을 구현하여 데이터베이스에 과부하가 걸리지 않도록 하세요. 반환되는 필드를 제한하세요. 모든 게시물 데이터를 반환하는 대신 ['ID', 'post_title']를 사용하세요. 트래픽이 많은 엔드포인트의 경우 Redis를 사용한 객체 캐싱을 고려하세요.

실제 사례: 최근 게시물을 위한 안전한 엔드포인트

주어진 사용자 ID에 대한 최근 게시물 제목을 반환하고 edit_posts 권한이 있는 사용자만 접근할 수 있는 엔드포인트를 구축해 보겠습니다. 전체 코드:

add_action('rest_api_init', function () {
  register_rest_route('myplugin/v1', '/user-posts/(?P<user_id>\d+)', array(
    'methods'             => 'GET',
    'callback'            => 'myplugin_user_posts_callback',
    'permission_callback' => function() { return current_user_can('edit_posts'); },
    'args'                => array(
      'user_id' => array(
        'required'          => true,
        'validate_callback' => function($param) { return is_numeric($param); },
        'sanitize_callback' => 'absint',
      ),
    ),
  ));
});

function myplugin_user_posts_callback($request) {
  $user_id = $request->get_param('user_id');
  $cache_key = 'myplugin_user_posts_' . $user_id;
  $posts = get_transient($cache_key);
  if (false === $posts) {
    $query = new WP_Query(array(
      'author'      => $user_id,
      'post_status' => 'publish',
      'fields'      => 'ids',
    ));
    $posts = $query->posts;
    if (!empty($posts)) {
      $titles = array();
      foreach ($posts as $post_id) {
        $titles[] = get_the_title($post_id);
      }
      set_transient($cache_key, $titles, 6 * HOUR_IN_SECONDS);
      return new WP_REST_Response($titles, 200);
    }
    return new WP_REST_Response(array(), 200);
  }
  return new WP_REST_Response($posts, 200);
}

이 엔드포인트는 user_id를 검증하고, 권한을 확인하며, 결과를 캐싱하고, 깨끗한 데이터를 반환합니다.

주의사항 및 일반적인 함정

  • REST 콜백 내에서 $_GET 또는 $_POST를 신뢰하지 마세요. 항상 $request->get_params()를 사용하세요.
  • 민감한 데이터(예: 사용자 이메일)를 노출하는 GET 엔드포인트에 대해서도 권한을 검증하세요.
  • 오류 처리: 콜백 내에서 wp_send_json_error()를 사용하지 말고 일관성을 위해 WP_Error 또는 WP_REST_Response를 반환하세요.
  • 테스트: Postman 또는 nonce 헤더가 포함된 curl과 같은 도구를 사용하여 요청을 시뮬레이션하세요.
  • Global $wpdb: SQL 인젝션을 방지하기 위해 데이터베이스 쿼리를 $wpdb->prepare()로 감싸세요.
  • 속도 제한: 엔드포인트가 공개된 경우 트랜션트를 사용하여 커스텀 속도 제한을 구현하는 것을 고려하세요.

결론

안전한 WordPress REST 엔드포인트를 구축하려면 경로 등록, 입력 처리, 권한, nonce 및 성능 등 모든 계층에 주의를 기울여야 합니다. 여기에 설명된 방법, 특히 검증/정제 콜백이 포함된 args 정의, 항상 permission_callback 설정, 비용이 많이 드는 쿼리 캐싱, nonce 사용을 따르면 검증을 견딜 수 있는 엔드포인트를 만들 수 있습니다. 이러한 패턴을 모든 커스텀 경로에 적용하고 인증된 요청과 인증되지 않은 요청 모두를 철저히 테스트하세요. 관련 보안 주제에 대한 자세한 내용은 WordPress Plugin Security & Performance 가이드를 확인하세요. 이제 API를 안전하게 만드세요.

Sources (5)