WordPress ユーザーの一覧表示対策
更新日:2025年07月25日
作成日:2019年01月18日
ユーザー情報が漏れるリスク
WordPressでは、例えば「サイトのURL/?author=1」などのURLにアクセスすることで、投稿者のユーザー名(ログインID)が閲覧者に知られてしまう可能性があります。
表示されるのは厳密には「user_nicename」という項目で、「user_login」(ログイン時に使用するID)とは異なる項目ですが、初期設定のままだと「user_login」がそのまま「user_nicename」として使用されているため、実質的にログインIDが外部に判明してしまうケースが少なくありません。
また、WP REST API がデフォルトで有効化されているため、認証なしでもユーザー情報などのデータが外部から取得できる状態になっています。
ユーザー名が知られるだけで即座に大きな被害が出るわけではありませんが、ブルートフォース攻撃や辞書攻撃の成功率を高める要因になります。
そのため、複雑なパスワードの設定や、ログイン試行制限の導入(例:Limit Login Attempts Reloaded や SiteGuard WP Plugin プラグイン)など、一覧表示対策以外の基本的なセキュリティ対策と組み合わせることが重要です。関連ページ:WordPress セキュリティ対策の基本
最も効果的かつ安全な対策は user_nicename の適切な設定(変更)です。これだけでも複数の漏洩経路からのリスクを軽減できます。
対策方法
以下のいずれかの(または両方の)方法を導入することで、author アーカイブによるリスクの基本的な対策が可能です。WP REST API に対しては user_nicename をログイン ID とは異なるものに変更する や 必要に応じて REST API を無効にするなど別途対応します。
- 著者アーカイブページへのアクセスを制御する
- user_nicename をログイン ID(user_login)とは異なるものに変更する
著者アーカイブページへのアクセスを制御する場合、以下の2つのケースが考えられます。
- 著者アーカイブページが必要ない
- 「?author=1」も著者アーカイブページ(/author/username/)も両方ブロック(完全に非公開にする)
- 著者アーカイブページを公開する
- 「?author=1」のようなアクセスはブロックし、著者アーカイブページ(/author/username/)は公開
user_nicename をログイン ID とは異なるものに変更する
WordPress では、ユーザー作成時に user_nicename が自動的に user_login(ログイン ID)と同じ値で設定されます。この user_nicename は、投稿者アーカイブページの URL(例:/author/ユーザー名/)や、REST API エンドポイント(例:/wp-json/wp/v2/users)など、公開 URL に利用される情報です。
そのため、user_nicename にログイン ID がそのまま使われていると、第三者にログイン ID を知られるリスクがあります。
user_nicename は「公開用スラッグ」であり、ブログ上の表示名(display_name)やニックネーム(nickname)とは異なります。
対策の効果とポイント
- この変更により、ログイン ID が URL や API 経由で第三者に知られるのを防ぐことができます。
- 著者アーカイブ・API・検索エンジンのキャッシュなど、複数の経路からの情報漏えいを同時に防止できる、もっとも根本的な対策です。
注意点
- ユーザー作成直後に変更するのが理想的です(公開前であれば影響なし)。
- すでに公開済みのユーザーの場合、/author/旧nicename/ のリンクが 404 になる可能性があるため、必要に応じてリダイレクト設定(例:.htaccess 等)を行うと SEO への影響を抑えられます。
- user_nicename は英数字とハイフン(-)のみで構成されるスラッグで、日本語などを設定すると自動的にエンコードまたは sanitize_title() による変換が行われます。
変更方法
user_nicename の変更は WordPress 管理画面からは直接できません。user_nicename をログイン ID(user_login)と異なるものにするには、以下のような方法があります。
- データベースを直接編集
- プラグインを利用
- functions.php で変更
- ユーザー編集画面で変更(functions.php に記述)
データベースを直接編集
phpMyAdmin などを使用してデータベースを直接編集して user_nicename を変更することができます。
wp_users テーブルにある user_nicename フィールドを編集します。
該当ユーザーの行を探し、user_nicename の値を任意の文字列(ログイン ID と異なる値)に変更します。
注意: URL 構造(example.com/author/スラッグ)にも影響するため、スラッグとして適切な文字列を設定する必要があります(半角英数字、ハイフンのみ)。
プラグインを利用
専用のユーザー編集プラグイン(例:Edit Author Slug)を使えば、user_nicename に相当する「著者スラッグ」を GUI から安全に変更できます。
この方法はデータベースを直接編集するよりも安全かつ便利です。
functions.php で変更
wp_update_user() 関数などを使って変更することが可能です。
以下は functions.php で wp_update_user() を使って、ユーザーID が 1 のユーザーの user_nicename を変更する例です。以下のコードは一度アクセスするだけで実行される(ページを開くたびに毎回実行される)ので、実行後は必ず add_action() をコメントアウトします。
user_nicename はスラッグ(URLに使う文字列)なので、記号などを含めるとトラブルの元になるため、必ず sanitize_title_with_dashes() や sanitize_title() を通して英数字・ハイフンだけに整えます。
[注意]以下の例では、わかりやすいように新しい user_nicename を foo-public としていますが、実際にはログイン IDが推測されにくい名前にする必要があります。
function safe_update_user_nicename_on_init() {
$user_id = 1; // 対象ユーザーID
$new_nicename = 'foo-public'; // 新しい user_nicename
$slug = sanitize_title_with_dashes($new_nicename);
// すでに同じスラッグが使われていないか確認
$existing_user = get_user_by('slug', $slug);
if ($existing_user && $existing_user->ID != $user_id) {
error_log('すでに使われているスラッグのため、更新を中止しました。');
return;
}
// 更新
$result = wp_update_user([
'ID' => $user_id,
'user_nicename' => $slug
]);
if (is_wp_error($result)) {
error_log('ユーザー更新に失敗しました: ' . $result->get_error_message());
} else {
error_log('ユーザーID ' . $user_id . ' の user_nicename を ' . $slug . ' に更新しました。');
}
}
// 実行が完了したら以下の add_action() をコメントアウト
add_action('init', 'safe_update_user_nicename_on_init');
確認
確認方法としては、phpMyAdmin などでデータベースを開いて、wp_users テーブルの user_nicename カラムの値を直接確認するのが確実です。
/author/{新しいuser_nicename} にアクセスして確認することもできます。新しい user_nicename で著者アーカイブが表示されれば成功です(旧スラッグでアクセスすると 404 にります)。
但し、サーバーやブラウザキャッシュにより、すぐに新しい user_nicename が反映されないこともあるため確認するときは、ブラウザキャッシュをクリアするか、シークレットモードでチェックすると確実です。
また、サーバーの error_log を確認することもできます(上記のコードでは成功でも失敗でもログを出力)。
error_log() により出力されるサーバーの error_log ファイルの場所は、環境によって異なります。例えば、MAMP/XAMPP(ローカル開発環境)の場合、以下にあります。
- MAMP: /Applications/MAMP/logs/php_error.log
- XAMPP: xampp/php/logs/php_error_log
また、開発環境の場合、WordPress 自体のデバッグ設定(wp-config.php)で以下が設定されていれば、/wp-content/debug.log に出力されます(※ 本番環境ではこれらは false にします)。
但し、WP_DEBUG_LOG が true になっている場合、WordPress が PHP エラーや error_log() の出力をキャッチし、wp-content/debug.log に記録するため、通常のサーバーの error_log(MAMP の場合は php_error.log など)には出力されません。
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
複数ユーザーの user_nicename 一括更新
以下は複数ユーザーの user_nicename 一括更新する例です。$users_to_update に、更新対象のユーザーIDと新しい user_nicename をセットします。
この場合も、前述のコード同様、実行後は必ず add_action() をコメントアウトします。
function bulk_update_user_nicenames() {
// 更新したいユーザーのリストを用意(ユーザーID と新しい user_nicename を指定)
$users_to_update = [
// user_id => 新しい user_nicename
2 => 'alice-public',
3 => 'bob-public',
4 => 'charlie-public',
// 必要に応じて追加
];
foreach ($users_to_update as $user_id => $new_nicename) {
$slug = sanitize_title_with_dashes($new_nicename);
// すでに同じスラッグが存在するか確認
$existing_user = get_user_by('slug', $slug);
if ($existing_user && $existing_user->ID != $user_id) {
error_log("スラッグ '{$slug}' は既にユーザーID {$existing_user->ID} が使用しています。スキップしました。");
continue;
}
// スラッグを更新
$result = wp_update_user([
'ID' => $user_id,
'user_nicename' => $slug,
]);
if (is_wp_error($result)) {
error_log("ユーザーID {$user_id} の更新に失敗しました: " . $result->get_error_message());
} else {
error_log("ユーザーID {$user_id} の user_nicename を '{$slug}' に更新しました。");
}
}
}
// 実行が完了したら以下の add_action() をコメントアウト
add_action('init', 'bulk_update_user_nicenames');
ユーザー ID を確認する方法
WordPress の標準のダッシュボード(管理画面)では、ユーザーIDは表示されず、通常の「ユーザー一覧」や「プロフィール」画面からは user_id(数値)は確認できないようになっています。
古いバージョンの WordPress ではユーザーのプロフィールページの URL からユーザーID を確認できましたが、現在はリファラ情報のみが付与されるようになっていてできないようです。
ユーザー ID を確認する方法としては、以下のような方法があります。
データベース(wp_users テーブル)で確認
phpMyAdmin などで wp_users テーブルの ID カラムでユーザーID を確認できます。
REST API の出力で確認
REST API の出力を制限していない場合、以下のいずれかにアクセスしてユーザーID を確認できます。
- サイトのURL/wp-json/wp/v2/users
- サイトのURL/?rest_route=/wp/v2/users
functions.php にカスタムコードを追加して表示
以下のコードを functions.php に追加すると、ユーザー一覧画面に ID 列を追加できます。
// ユーザー一覧画面にID列を追加
add_filter('manage_users_columns', function($columns) {
if (current_user_can('administrator')) {
$columns['user_id'] = 'ID';
}
return $columns;
});
add_action('manage_users_custom_column', function($value, $column_name, $user_id) {
if ($column_name == 'user_id' && current_user_can('administrator')) {
return $user_id;
}
return $value;
}, 10, 3);
上記を functions.php に追加すると、管理者としてログインしていれば、以下のようにユーザー一覧の画面でユーザーIDのカラムが追加されます。
ログインIDからユーザーIDを取得する方法
以下のように get_user_by() 関数を使って、ログインID(ユーザー名))からユーザーIDを取得できます。
$user_login = 'example_user'; // ログインID(ユーザー名)
$user = get_user_by('login', $user_login);
if ($user) {
$user_id = $user->ID;
error_log('ユーザーIDは ' . $user_id . ' です。');
} else {
error_log('指定されたユーザーは存在しません。');
}
get_user_by() の第一引数には検索条件の種類を指定できます。
種類 | 意味 |
---|---|
id | ユーザーID |
slug | user_nicename(スラッグ) |
メールアドレス | |
login | ログインID(user_login) |
戻り値はユーザーオブジェクト(WP_User)ですが、存在しない値を指定すると false が返ります。また、ログインIDの大文字・小文字は区別されません(user_login は内部的に小文字で保存されるため)。
ユーザー編集画面で変更
functions.php に以下を記述して、ユーザー編集画面で user_nicename を変更することもできます。
show_user_profile と edit_user_profile アクションでプロフィール画面に独自の nicename フィールドを追加し、personal_options_update と edit_user_profile_update で値を保存します。
プロフィール画面に独自の nicename フィールドを追加する際は、「ニックネーム」と「ブログ上の表示名」の間に表示するように、フィールドを追加後に JavaScript を使って、要素を移動しています。また、念の為、エラー表示のスタイルをクリアします。
保存の際は、指定された nicename が既存ユーザーと重複している場合は user_profile_update_errors アクションを使ってエラーを表示し(保存を中断して)、現在(これまで)の nicename を復元します。
/**
* ユーザー編集画面に nicename フィールドを追加する
*
* @param WP_User $user 現在編集しているユーザーオブジェクト
*/
function my_add_nicename_field($user) {
// ユーザーデータを取得
$userdata = get_userdata($user->ID);
?>
<table class="form-table" id="my-nicename-field">
<tr>
<th><label for="user_nicename">ナイスネーム(nicename)</label></th>
<td>
<input name="user_nicename" id="user_nicename" value="<?php echo esc_attr($userdata->user_nicename); ?>" class="regular-text" />
<span class="description">ユーザー名と異なる値を指定</span>
</td>
</tr>
</table>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 出力された nicename フィールドを、JavaScriptで「ニックネーム」と「ブログ上の表示名」の間に移動
// ニックネーム(nickname)フィールドの input 要素を探す
const nicknameInput = document.getElementById('nickname');
if (!nicknameInput) return; // 見つからなければ終了
// ニックネームフィールドの <tr> 要素を取得
const nicknameRow = nicknameInput.closest('tr');
// nicename フィールドの <tr> 要素を取得
const nicenameRow = document.querySelector('#my-nicename-field tr');
// ニックネームフィールドの直後に nicename フィールドを挿入
if (nicknameRow && nicenameRow) {
nicknameRow.parentNode.insertBefore(nicenameRow, nicknameRow.nextSibling);
}
// nicename フィールドのスタイルとエラーをリセット
const nicenameInput = document.getElementById('user_nicename');
if (nicenameInput) {
nicenameInput.style.backgroundColor = ''; // 背景色をリセット
nicenameInput.style.padding = '6px';
const existingError = document.querySelector('.nicename-error-message');
if (existingError) existingError.remove(); // もしエラーメッセージが残ってたら消す
}
});
</script>
<?php
}
// ユーザー自身がプロフィールを見る・編集する画面で nicename フィールドを表示
add_action('show_user_profile', 'my_add_nicename_field');
// 管理者が他人のプロフィールを編集する画面でも nicename フィールドを表示
add_action('edit_user_profile', 'my_add_nicename_field');
/**
* ユーザーの nicename を保存・更新する
*
* @param int $user_id 保存対象のユーザーID
*/
function my_update_nicename($user_id) {
// 現在のユーザーがこのユーザーを編集できるか確認
if (! current_user_can('edit_user', $user_id)) {
return false;
}
// nicename フィールドが送信されているかチェック
if (! isset($_POST['user_nicename'])) {
return;
}
// 入力された nicename を正規化(スラッグ化)
$nicename = sanitize_title_with_dashes($_POST['user_nicename']);
// すでに同じ nicename を持つ他のユーザーがいないかチェック
$existing_user = get_user_by('slug', $nicename);
// nicename が既存ユーザーと重複している場合はエラーを表示
if ($existing_user && $existing_user->ID != $user_id) {
// エラー登録 ( $errors->add() でエラーを登録し、エラーメッセージを出して、保存を止める)
add_action('user_profile_update_errors', function ($errors) use ($nicename) {
$errors->add(
'user_nicename_exists',
sprintf(
'ナイスネーム「%s」はすでに使用されています。別のものを指定してください。',
esc_html($nicename)
)
);
// エラー時に JavaScript を出力(admin_footer で JS を設定)
add_action('admin_footer', function () use ($nicename) {
?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const nicenameInput = document.getElementById('user_nicename');
if (!nicenameInput) return;
// エラーが出ていなければエラーメッセージを追加
if (!document.querySelector('.nicename-error-message')) {
// 入力フィールドに背景色をつける
nicenameInput.style.backgroundColor = '#fae3ec';
// エラーメッセージを作成
const errorMessage = document.createElement('div');
errorMessage.innerHTML = 'ナイスネーム「<?php echo esc_html($nicename); ?>」はすでに使用されています。<br>以前のナイスネームに復元しました。<br>変更する場合は新しい値を入力してください。<br>変更しない場合も必ず「ユーザーを更新」ボタンをクリックしてください。';
errorMessage.style.color = 'red';
errorMessage.style.marginTop = '8px';
errorMessage.style.lineHeight = '1.5';
errorMessage.classList.add('nicename-error-message');
// エラーメッセージをフィールドの下に追加
nicenameInput.insertAdjacentElement('afterend', errorMessage);
// フィールドへスムーズスクロール
nicenameInput.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
});
</script>
<?php
});
});
// エラー時は現在の nicename を復元
$current_userdata = get_userdata($user_id);
$nicename = $current_userdata->user_nicename;
}
// nicename を更新
$userdata = array(
'ID' => $user_id,
'user_nicename' => $nicename,
);
wp_update_user($userdata);
}
// ユーザー自身がプロフィールを保存する時に nicename を保存
add_action('personal_options_update', 'my_update_nicename');
// 管理者が他人のプロフィールを保存する時も nicename を保存
add_action('edit_user_profile_update', 'my_update_nicename');
上記を保存して、ユーザー編集画面を開くと「ニックネーム(必須)」と「ブログ上の表示名」の間にナイスネームのフィールドが追加され、user_nicename を設定できるようになります。
設定した nicename が既存ユーザーの user_nicename と重複している場合はエラーを表示し、入力された値はクリアされ元の値が復元されます。
上記の状態で、ページを再読込すると、「無効なユーザー ID です」とだけ表示されるので、エラーメッセージには「必ず更新ボタンをクリックしてください」と表示しています。もし、「無効なユーザー ID です」とだけ表示された場合は、ブラウザの「戻る」ボタンをクリックする必要があります。
使用しているアクションフック
アクション | 説明 |
---|---|
show_user_profile | 自分自身のプロフィールページ(/wp-admin/profile.php)を表示するときに呼ばれるアクション。自分のプロフィール画面に独自のフィールドを追加したいときなどに使用。 |
edit_user_profile | 他のユーザーのプロフィールページ(/wp-admin/user-edit.php)を表示するときに呼ばれるアクション。管理者が「他のユーザーのプロフィールを編集」する画面で、独自のフィールドを追加したいときに使用。 |
personal_options_update | 自分自身のプロフィールページを保存するときに呼ばれるアクション。フォーム送信時に「自分の独自のフィールドの値を保存する」処理を記述。 |
edit_user_profile_update | 他のユーザーのプロフィールページを保存するときに呼ばれるアクション。管理者などが「他人のプロフィールを編集して保存」したときに、独自のフィールドの値を保存する処理を記述。 |
user_profile_update_errors | ユーザーのプロフィール(ユーザー情報)を更新しようとしたときに、バリデーションエラー(入力エラー)を追加するために使うアクション。$errors というエラーオブジェクト(WP_Error)を受け取り、この $errors にエラーを追加すると、保存がストップし、画面上にエラーメッセージが表示される |
admin_footer | 管理画面(ダッシュボード)で、HTMLの</body>タグ直前にフックされるアクション。管理画面の一番下にHTMLやJSを追加したいときに使うフック |
WP REST API を無効にする
WordPress 4.7 から「WP REST API」がデフォルトで有効になっています。そのため認証無しでも投稿記事やユーザー情報などのデータを外部から取得する事ができるようになっています。
例えば、以下のアドレスにアクセスしてみると、json 形式のユーザーのデータが表示されます。
「サイトのURL/wp-json/wp/v2/users」や「サイトのURL/?rest_route=/wp/v2/users」
REST APIを無効化することで、匿名の外部アクセスを防止できますが、すべて無効化すると、REST API に依存する WordPress 管理画面の機能(Gutenberg エディターなど)が動作しなくなります。
そのため、無効にする場合は、以下のように rest_authentication_errors フィルターを使って、ログインしていない場合にのみ無効にする方法が推奨されています(REST API Handbook )。
// ログインしていない場合、REST API へのアクセスを制限する
add_filter('rest_authentication_errors', function ($result) {
// 以前の認証チェックが適用されている場合は、その結果を変更せずに渡す(返す)
if ( true === $result || is_wp_error( $result ) ) {
return $result;
}
// ログイン済みユーザーは許可
if (is_user_logged_in()) {
return $result;
}
// ログインしていない場合はエラーを返す
return new WP_Error(
'rest_not_logged_in',
__('You are not currently logged in.', 'text-domain'),
array('status' => 401)
);
});
但し、この方法だと、ユーザーがログインしていないと Contact Form7 などの REST API を使用しているプラグインが機能しなくなってしまいます。
特定 API(プラグイン)だけ例外許可
前述のコードの場合、ログインしていないリクエストに対して無条件に WP_Error を返して、REST API を強制的に止めているため、Contact Form 7 などの「ログインしていないユーザー」も対象にして REST API 通信しているプラグインは正常動作できなくなってしまいます。
そのため、プラグインが使う特定のエンドポイントだけを許可する処理を追加する必要がありますが、以下のいずれかの方法で対応可能です。
rest_authentication_errors を利用
以下は rest_authentication_errors フィルターを使って、Embed(oEmbed)や Contact Form 7、 Akismet プラグイン、及びカスタムエンドポイントを許可する例です。
許可するルート($allowed_routes)には、REST APIのエンドポイントの /wp-json/ 以降、最初のパス(namespace)にスラッシュを付けた形で指定します(例:/contact-form-7/v1 など)。
add_filter('rest_authentication_errors', function ($result) {
if (true === $result || is_wp_error($result)) {
return $result;
}
// ログイン済みユーザーは許可(管理画面の機能を使えるように)
if (is_user_logged_in()) {
return $result;
}
// REST API リクエストルートを取得
global $wp;
// /wp-json/ 以降のパスが取れる(例: /contact-form-7/v1 や /wp/v2/posts など)
$rest_route = isset($wp->query_vars['rest_route']) ? $wp->query_vars['rest_route'] : '';
// 許可するルート
$allowed_routes = [
'/oembed/1.0', // Embed 用
'/contact-form-7/v1', // Contact Form 7 用
'/akismet/v1', // Akismet 用
'/my-theme-api/v1/posts', // カスタムエンドポイント 用
];
// 対象のルートは許可
foreach ($allowed_routes as $allowed_route) {
if (strpos($rest_route, $allowed_route) === 0) {
return $result; // 許可対象ならOK
}
}
// それ以外はエラーを返す
return new WP_Error(
'rest_not_logged_in',
__('You are not currently logged in.', 'text-domain'),
array('status' => 401)
);
});
上記コードでは、ログイン済みユーザーはすべて許可していますが、 投稿・固定ページの編集権限があるユーザー(例: Gutenberg 使用者)のみ許可する場合は7-9行目を以下のように書き換えます。
if (current_user_can('edit_posts') || current_user_can('edit_pages')) {
return $result;
}
上記のコードにより、ログインユーザー以外は、「サイトのURL/wp-json/wp/v2/users」や「サイトのURL/?rest_route=/wp/v2/users」などにアクセスしても、許可したルート以外は表示されません。
rest_pre_dispatch を利用
以下は、rest_pre_dispatch フィルターを使って、引数 $request の get_route() メソッドをで、ルート判定して特定 API(プラグイン)だけ例外許可する例です。
前述の rest_authentication_errors を使ったコード同様、特定のプラグイン(oEmbed, Contact Form 7, Akismet)だけ許可し、あとは制限(エラー)します。許可の基準は namespace になるので先頭のスラッシュなしで指定します。環境に合わせて30-34行目部分を変更します。
また、以下では編集権限があるユーザーのみ許可していますが、ユーザーがログインしていれば許可するには9行目の判定を is_user_logged_in() に変更します。
// 編集権限のあるユーザー及び指定したプラグインに REST API を許可(それ以外は無効にする)
add_filter('rest_pre_dispatch', function ($result, $server, $request) {
// 念のため、リクエストオブジェクトの型チェック ( WP_REST_Request 以外なら処理しない )
if (! ($request instanceof WP_REST_Request)) {
return $result;
}
// 投稿・固定ページの編集権限があるユーザーなら許可(例: Gutenberg使用者)
if (current_user_can('edit_posts') || current_user_can('edit_pages')) {
return $result;
}
// ルート取得(先頭スラッシュを除去)
$route = ltrim($request->get_route(), '/');
// ルートが空の場合は REST API を無効化
if (empty($route)) {
return new WP_Error(
'rest_disabled',
__('The REST API on this site has been disabled.', 'text-domain'),
array('status' => 403)
);
}
// スラッシュでルートを分割して namespace を抽出(例: "contact-form-7/v1")
$parts = explode('/', $route);
$namespace = isset($parts[0]) && isset($parts[1]) ? $parts[0] . '/' . $parts[1] : '';
// 許可する namespace 一覧(oEmbed、Contact Form 7、Akismet)
$permitted_namespaces = [
'oembed/1.0', // Embed 用
'contact-form-7/v1', // Contact Form 7 用
'akismet/v1', // Akismet 用
];
// 許可リストに含まれていれば通過
if (in_array($namespace, $permitted_namespaces, true)) {
return $result;
}
// それ以外は REST API を無効化(ステータス 403)
return new WP_Error(
'rest_disabled',
__('The REST API on this site has been disabled.', 'text-domain'),
array('status' => 403)
);
}, 10, 3);
上記のコードにより、編集権限があるユーザーも含め、「サイトのURL/wp-json/wp/v2/users」や「サイトのURL/?rest_route=/wp/v2/users」などにアクセスしても、許可したルート以外は表示されません。
除外するプラグインを追加
除外したいプラグインが増えた場合は、上記コードの $permitted_namespaces(30-34行目)にそのプラグインのエンドポイントの名前空間を追加で指定します。
REST API を利用しているプラグインの名前空間は「サイトのURL/wp-json/」でアクセスして表示されるデータの namespaces で確認できます(REST API を有効にしておきます)。
rest_authentication_errors を使用する場合との違い
どちらのコードも「処理結果」的には、特定プラグインは許可、それ以外は制限するので同じですが、実行タイミングが異なります。
rest_authentication_errors のコードの場合、認証段階(APIアクセスのかなり早い段階)で実行されますが、rest_pre_dispatch のコードではリクエストの直前(データ取得直前)に実行されます。
/wp-json/wp/v2/users など許可したルート以外にアクセスした場合、rest_authentication_errors のコードでは、ログインユーザーはデータが表示されますが、rest_pre_dispatch のコードでは、編集権限があるユーザーでも表示されません。
この例の場合、rest_authentication_errors のコードでは 401 Unauthorized を返していますが、rest_pre_dispatch のコードでは 403 Forbidden を返しています。
「未ログインユーザー」に厳しく制限したい(API認証時にすぐ弾きたい)場合は rest_authentication_errors を使用し、「一部権限のあるユーザー(編集者など)」にも許可しつつ、柔軟に制御したい場合は rest_pre_dispatch を使うなどが考えられます。
WP REST API によるユーザー一覧表示を抑制
WordPress の WP REST API 全体を無効にするのではなく、/wp-json/wp/v2/users などのユーザー一覧エンドポイントのみを制限する方法です。
rest_endpoints フィルターを使って REST API のエンドポイント情報を取得し、ユーザー情報に関するエンドポイントが存在する場合に、その permission_callback をログイン済みかどうかで判断するクロージャに差し替えます。
これにより、未ログイン状態でのユーザー一覧の取得を防ぎ、ユーザー名の列挙(User Enumeration)対策になります。以下がそのコードです。
// REST API のエンドポイント定義を変更するためのフィルターを追加
add_filter('rest_endpoints', function ($endpoints) {
// 対象とするエンドポイント(ユーザー一覧と個別ユーザー情報)
foreach (['/wp/v2/users', '/wp/v2/users/(?P<id>[\d]+)'] as $route) {
// 指定したエンドポイントが実際に存在しているか確認
if (isset($endpoints[$route])) {
// 該当ルートに定義された各エンドポイント(HTTP メソッド別)をループ処理(&$endpoint の & は参照渡し)
foreach ($endpoints[$route] as &$endpoint) {
// エンドポイントが配列形式で、permission_callback が定義されているか確認
if (is_array($endpoint) && isset($endpoint['permission_callback'])) {
// 元の permission_callback を変数に保持しておく(他のプラグインやコアの挙動を維持するため)
$original_callback = $endpoint['permission_callback'];
// 新しい permission_callback を定義(ログイン済みかつ、元の判定も通過する必要あり)
$endpoint['permission_callback'] = function ($request) use ($original_callback) {
// is_user_logged_in() が true かつ、元の permission_callback も true を返す場合のみ許可
return is_user_logged_in() && call_user_func($original_callback, $request);
};
}
}
}
}
// 変更を加えたエンドポイント情報を返す
return $endpoints;
});
新しい permission_callback の定義では、function ($request) use ($original_callback) という無名関数(クロージャ)を使い、外部の $original_callback(元のアクセス判定関数)を中で利用できるようにしています。
$request は、REST API に送信されたリクエストの内容を表す WP_REST_Request オブジェクトです。
call_user_func( $original_callback, $request ) は、元の permission_callback を $request を引数として実行する処理を意味します。call_user_func() は、関数名やクロージャを文字列や変数から動的に呼び出せる PHP の標準関数です。
このようにすることで、他のプラグインや WordPress コアが本来定義している権限チェックロジックを壊すことなく、追加で is_user_logged_in() によるログインチェックも組み込むことができます。
上記のコードを functions.php に記述すると、ログインしていない状態で「サイトのURL/wp-json/wp/v2/users」や「サイトのURL/?rest_route=/wp/v2/users」にアクセスすると、401 Unauthorized が返るようになります(ユーザー一覧の列挙を防止できます)。
ログインしていても 401 Unauthorized が返ってくる
上記のようなコードを記述しても、ブラウザで直接 /wp-json/wp/v2/users にアクセスした場合、ログインしていても X-WP-Nonce ヘッダーが送信されないため、WordPress は「未認証」と判定し、401 Unauthorized を返します。
REST API で「ログイン状態」として認識されるには、ログイン中のセッション Cookie に加えて、Nonce ヘッダー(X-WP-Nonce)またはクエリパラメータの _wpnonce が必要です。
例えば、テスト用に以下のコードを functions.php に記述すると、ログイン時に「ユーザー一覧を確認する」というリンクがフッターに出力されます。ログインしていればユーザー一覧が表示され、ログアウトしていれば 401 Unauthorized が返されます。
function my_users_rest_link() {
$nonce = wp_create_nonce('wp_rest');
$url = home_url('/?rest_route=/wp/v2/users&_wpnonce=' . $nonce);
echo '<p><a href="' . esc_url($url) . '">ユーザー一覧を確認する</a></p>';
}
add_action('wp_footer', 'my_users_rest_link');