WordPressのAjaxでセキュアな通信を行う方法を、サンプルコードを使って説明します。
nonceを使ってかんたんに実装します。
noneceは意味のないランダムな文字列で、ワンタイムパスワード認証っぽいことをすると言ったほうが分かりやすいかも。
nonceとは何か?
nonceは使い捨てのランダムな値です。これを使ってサーバが正当なhttpsリクエストを受信したことを確認します。
nonceはCSRF対策です。
実装はかんたんです。さっそく見てみましょう。
サンプルコードは『WordPressでAjaxを使う(公開サイト編)』を元にすすめます。WordPressでのAjaxの実装方法が分からない人は、こちらからどうぞ。
また、HTMLで<form>を使わず、ajaxでフォームを送信します。<form>を使った方法はさっきのリンク(...nonceのちがい...)にあるので割愛します。
JavaScriptのコーディング
ここでは、キャッシュのクリア処理をサーバーにリクエストします。
jsファイルのキュー登録
キュー登録のサンプルコードです。
add_action('admin_enqueue_scripts', function() {
$handle = 'js_handle_name';
wp_register_script( $handle, plugins_url( 'my-plugins/js/clear-cache.js', __FILE__ ), [ 'jquery' ], '1.0.0', true );
$localize = [
'ajax_url' => admin_url('admin-ajax.php'),
'action' => 'clear_cache',
'check_nonce' => wp_create_nonce( 'ajax-nonce' )
];
wp_localize_script( $handle, 'localize', $localize );
wp_enqueue_script( $handle );
} );
(公開サイトのときは'admin_enqueue_scripts'を'wp_enqueue_scripts'に変える。)
wp_localize_script()のパラメータに、生成したnonceのランダムな値を追加します。
$ajax = [
'check_nonce' => wp_create_nonce('ajax-nonce')
];
jsファイルの内容
次に、リクエストでnonceの値を送信するように、jsファイルに追加します。
下準備にHTMLにajaxのトリガーのボタンとurlリファラーを追加しておきます。
(リファラーはセキュア処理で使います。)
<?php wp_referer_field(); ?>
<button id="clear-cache">キャッシュのクリア</button>
<input type="hidden" name="_wp_http_referer" value="/example/">
<button id="clear-cache">キャッシュのクリア</button>
(function($) {
$(function() {
$('#clear-cache').on('click', function(){
"use strict";
const http_referer = $('input[name="_wp_http_referer"]').val();
$.ajax({
type: "POST",
url: localize.ajax_url,
dataType: 'text',
data: {
action: localize.action,
check_nonce: localize.check_nonce,
_wp_http_referer: http_referer
}
}).done(function(data, textStatus, jqXHR) {
outputMsg(data);
}).fail(function() {
outputMsg('cache clear error!!');
});
});
})
})(jQuery)
check_nonceというクエリにnonceの値をつけて、リクエストを送信します。
サーバの処理(phpのコーディング)
次の関数がajaxで送信したリクエストをサーバーで受信する処理です。
function clear_cache() {
$cache_file = '../cache/11111111111111111.cache';
$success = __('Complete clear cache');
$error = __('Clear cache failer');
$result = '';
if ( ! check_ajax_referer( 'ajax-nonce', 'check_nonce', false ) ) {
echo $error;
die();
}
if(file_exists($cache_file)) {
if(unlink($cache_file)) {
$result = $success;
} else {
$result = $error;
}
} else {
$result = $success;
}
echo $result;
die();
}
add_action('wp_ajax_clear_cache', 'clear_cache');
add_action('wp_ajax_nopriv_clear_cache', 'clear_cache'); // <--- 公開サイトのときだけ追加
check_ajax_referer()はAjax通信用のnonce値の確認を行います。
パラメータには、Ajax通信で指定したactionとnonceのクエリ変数名を指定します。
check_admin_referer()は管理画面用のnonce値の確認を行いますが、デフォルトではcheck_ajax_referer()と同じなので使う意味がありません。
管理画面からしか許可しないなら認証処理を追加しましょう。
/**
* admin認証の追加
*/
add_action('check_ajax_referer', function( $action, $result ) {
// urlリファラーチェックの追加
if ( $result ) {
$adminurl = strtolower( admin_url() );
$referer = strtolower( wp_get_referer() );
// 管理画面のurlからだけ許可する。
if ( false === strpos( $referer, $adminurl ) ) {
$result = false;
}
}
return $result;
}, 10, 2 );
公開サイトで特定のページからしか許可しないならurlを制限しましょう。
/**
* ajax認証の追加
*/
add_action('check_ajax_referer', function( $action, $result ) {
// URLリファラーチェックの追加
if ( $result ) {
$referer = strtolower( wp_get_referer() );
if ( 'ajax_test' === $action ) {
// 特定の投稿ページからだけ許可する。
$slug = 'nonce-test';
$post = get_posts('name=' . $slug);
$url = get_permalink( $post[0]->ID );
if ( ! strpos( $referer, $url ) ) {
$result = false;
}
}
}
return $result;
}, 10, 2 );
また、Ajaxでない通常のリクエスト用にwp_verify_nonce()も用意されています。
このあたりの関数のちがいをまとめました。
訂正
管理画面のurlリファラーチェックは、プラグインのアップデート処理が正常に動きません。
ほかの処理を入れるようにしましょう。
これでも分かるように、認証の追加処理は自分の作成したajaxだけでなくすべてのajax処理に影響します。
特定のajax処理にしたければactionに条件をつけましょう。
nonceだけで大丈夫か?
WordPressのnonceは有効期間があるので、正確にはワンタイムパスワードではなく、セキュアではないとも言えます。しかし、有効期間の設定の変更ができます。
たとえば、有効期間を4時間に変更するときは、functions.phpなどに次のように定義します。
add_filter( 'nonce_life', function () { return 4 * HOUR_IN_SECONDS; } );
有効期間をできるだけ短くして、ワンタイムパスワードに近いセキュリティができます。
それでも、これだけで大丈夫?と思うかもしれません。
このへんの気になるところもこちらに書いています。
クラスに処理を入れよう!
ajaxの処理は、キュー登録と受信した後処理をまとめたほうがいいです。クラスオブジェクトにしましょう。
class Clear_Cache_Ajax {
$is_publish = false;
$action = 'clear_cache';
$nonce_query = 'check_nonce';
function __construct( $is_publish = false ) {
$this->enqueue();
$this->add_authentication();
$this->action();
}
private function enqueue() {
$action = $this->action;
$handle = $this->is_publish ? 'wp_enqueue_scripts' : 'admin_enqueue_scripts';
$nonce_query = $this->nonce_query;
add_action( $handle, function() use ( $action, $nonce_query ) {
$handle = 'my_ajax_handle';
// urlの取り方は適宜変更。方法は割愛。
$js_url = plugins_url( 'my-plugins/js/clear-cache.js', __FILE__ );
wp_register_script( $handle, $js_url, [ 'jquery' ], '', true);
$localize = [
'ajax_url' => admin_url( 'admin-ajax.php' ),
'action' => $action,
$nonce_query => wp_create_nonce( $action )
];
wp_localize_script( $handle, 'localize', $localize );
wp_enqueue_script( $handle );
});
}
private add_authentication() {
add_action('check_ajax_referer', function( $action, $result ) {
// URLリファラーチェックの追加
if ( $result ) {
$referer = strtolower( wp_get_referer() );
if ( 'ajax_test' === $action ) {
// 特定の投稿ページからだけ許可する。
$slug = 'nonce-test';
$post = get_posts('name=' . $slug);
$url = get_permalink( $post[0]->ID );
if ( ! strpos( $referer, $url ) ) {
$result = false;
}
}
}
return $result;
}, 10, 2 );
}
private function action() {
$handle = 'wp_ajax_' . $this->action;
$handle_publish = 'wp_ajax_nopriv_' . $this->action;
$function = 'response';
add_action( $handle, [ $this, $function ] );
if ( $this->is_publish ) {
add_action( $handle_publish, [ $this, $function ] );
}
}
public function response() {
$action = $this->action;
$nonce_query = $this->nonce_query;
$cache_file = '../cache/11111111111111111.cache';
$success = __('Complete clear cache');
$error = __('Clear cache failer');
$result = '';
if ( ! check_ajax_referer( $action, $nonce_query, false ) ) {
echo $error;
die();
}
if( file_exists( $cache_file ) ) {
if( unlink( $cache_file ) ) {
$result = $success;
} else {
$result = $error;
}
} else {
$result = $success;
}
echo $result;
die();
}
}
/*----------------
Ajax通信を使う。
-----------------*/
new Clear_Cache_Ajax(); // <--- クラスインスタンスを作るだけ。
このクラスはまだ未完成です。actionを動的にすることで汎用的なAjax通信クラスにスケールアップできます。
このクラスを改修して完成度を高めるチャレンジをしてみてください。
ここではクラスの作り方を紹介しているわけではないのでこの辺にしておきます。