APIキー保護戦略:
ウェブサイトのセキュリティレベルを上げる二つの方法
ウェブサイトにAIなどの外部サービスを組み込む際、必ず必要になるのが「APIキー」です。これは、あなたのサイトがサービスを利用する正当なユーザーであることを証明するための「秘密の鍵」のようなものです。
しかし、この鍵を不用心に扱ってしまうと、第三者に盗まれ、不正利用されてしまう危険性があります。例えば、あなたのAPIキーを使って大量のAIリクエストを送信され、高額な利用料金を請求されるといった事態も起こりかねません。
この記事では、あなたの貴重なAPIキーを守るための2つの主要な戦略、「HTTPリファラー制限(門番を立てる方法)」と「サーバーサイドプロキシ(執事を雇う方法)」について、その仕組みと具体的な実装手順を詳しく解説します。
戦略1:HTTPリファラー制限(門番を立てる方法)
これは、最も手軽でありながら効果的なセキュリティ対策の一つです。Google AI StudioなどでAPIキーを発行する際、多くのサービスでは「このキーは、特定のウェブサイトからのアクセスでのみ有効にする」という設定ができます。これがHTTPリファラー制限です。
なぜ安全なのか?:「身分証明書」としての役割
HTTPリファラーとは、簡単に言えば「どのウェブページから来たか」を示す情報です。あなたが mative.info のリンクをクリックして別のサイトに移動した場合、移動先のサイトは「このユーザーは mative.info から来た」という情報を(リファラーとして)受け取ります。
APIキーにリファラー制限をかけることは、APIキーに「身分証明書」を持たせるようなものです。
設定: あなたはGoogleのサーバーに、「このAPIキーの身分証明書には https://mative.info と書いてあります」と登録します。
通信時: あなたのサイトがAPIキーを使ってGoogleにリクエストを送ると、Googleのサーバーはまず「このリクエストはどこから来たんだ?」とリファラーをチェックします。
検証: リファラーが https://mative.info であれば、「OK、身分証明書と一致する。通ってよし」と通信を許可します。
たとえ第三者があなたのウェブサイトのソースコードを読んでAPIキーを盗んだとしても、彼らが自分のウェブサイト(例:https://evil-site.com)からそのキーを使おうとすると、リファラーが一致しないため、Googleのサーバーは「身分証明書が違う。お前は偽物だ!」と判断し、通信をブロックしてくれます。
長所
- 設定が非常に簡単: Google AI Studioなどの管理画面でドメイン名を入力するだけで完了します。
- 効果が高い: ほとんどのカジュアルな不正利用を防ぐことができます。
短所
- リファラーの偽装: 技術に詳しい攻撃者は、リファラー情報を偽装して制限を突破する可能性もゼロではありません。
- 用途が限定される: この方法はブラウザからのアクセスを前提としているため、サーバー同士の通信などには使えません。
結論として、あなたの現在のユースケース(公開ウェブサイト上でのAI機能)では、このHTTPリファラー制限は非常に有効な第一の防御壁となります。
戦略2:サーバーサイドプロキシ(執事を雇う方法)
これは、より本格的で、格段に安全性が高いプロフェッショナルな方法です。APIキーをブラウザ(クライアントサイド)から完全に隠し、あなたのサーバー(サーバーサイド)に管理を任せます。
なぜこれが最強なのか?:「レストランの注文」の例え
この仕組みを、レストランでの注文に例えてみましょう。
今までの方法(クライアントサイド)
お客さん(ブラウザ)が、厨房(GoogleのAIサーバー)に向かって、「秘密の合言葉(APIキー)」を大声で叫んで直接注文している状態です。
これでは、店内にいる他の客に合言葉が丸聞こえです。リファラー制限(常連さんしか注文できないルール)はありますが、合言葉自体は聞こえてしまいます。
これからの方法(サーバーサイドプロキシ)
お客さん(ブラウザ)は、テーブルに来たホールスタッフ(あなたのWordPressサーバー)に「こういう料理が欲しい」とだけ静かに伝えます。
注文を受けたホールスタッフ(WordPress)が、厨房に直結した専用の通路を通り、厨房のスタッフだけが知っている合言葉(APIキー)を使って注文を伝えます。
これなら、他のお客さんに合言葉を聞かれる心配は一切ありません。
この頼れる「ホールスタッフ」の役割を、WordPressのREST APIという機能を使ってPHPで実装していきます。
⚙️ 実装手順:3ステップガイド
Step 1:秘密の合言葉(APIキー)をサーバーの金庫に保管する
まず、APIキーをブラウザからは絶対に見えない、サーバー上の安全な場所に保管します。WordPressでは、そのための最適な場所が wp-config.php
ファイルです。
wp-config.php
を開く: このファイルはWordPressのインストールフォルダのルートディレクトリにあります。サーバーにFTP接続するか、ホスティングサービスのファイルマネージャーを使ってアクセスします。- なぜ
wp-config.php
なのか?: このファイルは、ウェブサイトの公開領域(ドキュメントルート)の外に設置されることが多く、URLで直接アクセスすることができないため、非常に安全な保管場所とされています。 - APIキーを定数として定義する: ファイルを開き、
/* That's all, stop editing! Happy publishing. */
という行の直前に、以下の1行を追記します。'ここにあなたのAPIキーを貼り付けます'
の部分をご自身のキーに書き換えてください。
- Local: 「Site folder」ボタンをクリックして、あなたのWordPressプロジェクトのフォルダを開きます。
- Cursor: 開いたフォルダをCursorにドラッグ&ドロップしてプロジェクトとして開きます。ファイル一覧から
wp-config.php
を見つけて開きます。 - Cursor AI活用: もし追記する場所に迷ったら、AIに「wp-config.phpでデータベース設定の下あたりにAPIキーを定義したい」と尋ねることで、最適な場所を提案してもらえます。
// PHP: wp-config.php
define('GEMINI_API_KEY', 'ここにあなたのAPIキーを貼り付けます');
define()
はPHPの命令で、「GEMINI_API_KEY という名前の箱(定数)に、このAPIキーを入れて、中身を変えられないようにする」という意味です。これで、WordPress内のPHPコードから安全にキーを呼び出せるようになりました。
Step 2:ホールスタッフ(WordPress REST API)を教育する
次に、ブラウザからの注文を受け取り、Googleに代わりに問い合わせてくれるPHPのプログラムを書きます。これを子テーマの functions.php
ファイルに追記します。
- Cursor: プロジェクト内の
wp-content/themes/あなたの子テーマ名/functions.php
を開きます。 - Cursor AI活用: 以下のコードを貼り付けた後、理解できない関数(例:
register_rest_route
)があれば、その部分を選択してAIに「このPHP関数は何をしていますか?」と質問することで、その場で詳しい解説を得られます。 - Local:
functions.php
を保存したら、Localで動いているサイトがエラーで停止(真っ白に)していないか確認します。もしエラーが出たら、Cursorに戻ってAIにエラー内容を伝えて修正を依頼します。
// PHP: functions.php
// WordPressのREST APIに、私たちの専用窓口(エンドポイント)を追加する関数
function register_gemini_api_route() {
register_rest_route( 'my-ai/v1', '/query', [
'methods' => 'POST', // POSTメソッドでデータを受け取る
'callback' => 'handle_gemini_api_request', // 実際に処理する関数を指定
'permission_callback' => '__return_true' // 誰でもアクセス可能に(本格運用では要認証)
]);
}
add_action( 'rest_api_init', 'register_gemini_api_route' );
// ブラウザからのリクエストを処理する本体の関数
function handle_gemini_api_request( WP_REST_Request $request ) {
// ステップ1で設定したAPIキーを安全に取得
$api_key = defined('GEMINI_API_KEY') ? GEMINI_API_KEY : '';
if ( empty($api_key) ) {
return new WP_Error( 'no_api_key', 'APIキーがサーバーに設定されていません。', [ 'status' => 500 ] );
}
// JavaScriptから送られてきたプロンプトを取得
$prompt = $request->get_param('prompt');
if ( empty($prompt) ) {
return new WP_Error( 'no_prompt', 'プロンプトが空です。', [ 'status' => 400 ] );
}
$api_url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=' . $api_key;
$body = [ 'contents' => [ [ 'parts' => [ [ 'text' => $prompt ] ] ] ] ];
// WordPressの安全な方法で、サーバーからGoogleへリクエストを送信
$response = wp_remote_post( $api_url, [
'headers' => [ 'Content-Type' => 'application/json' ],
'body' => json_encode( $body ),
'timeout' => 60,
]);
if ( is_wp_error( $response ) ) {
return new WP_Error( 'api_error', 'APIとの通信に失敗しました。', [ 'status' => 500 ] );
}
$response_body = json_decode( wp_remote_retrieve_body( $response ), true );
return new WP_REST_Response( $response_body, 200 );
}
何をしているか?:
register_rest_route
:yoursite.com/wp-json/my-ai/v1/query
という、あなた専用のAPI窓口(エンドポイント)を作っています。handle_gemini_api_request
: この窓口に来た注文を処理するメインの関数です。wp_remote_post
: WordPressが推奨する、外部のサーバーと安全に通信するための関数です。これを使うことで、サーバーの設定に依存しにくい安定した通信が可能になります。
Step 3:お客さん(JavaScript)の注文方法を書き換える
最後に、今までGoogleに直接注文していたJavaScriptを、新しく作ったホールスタッフ(WordPressのAPI窓口)に注文するように変更します。フッターなどに追加したJavaScriptコードの callGemini
関数を、以下のようにまるごと書き換えます。
- Cursor: あなたのサイトでAI機能を動かしているJavaScriptファイルを開き、
callGemini
関数を以下のコードに置き換えます。 - Local: Localの「Open site」から開いたブラウザで、AI機能が動作するか試します。Chromeの場合、「右クリック→検証」でデベロッパーツールを開き、「Console」タブにエラーが出ていないか、「Network」タブで
/wp-json/my-ai/v1/query
への通信が成功しているか(ステータス200)を確認します。 - Cursor AI活用: もしブラウザのコンソールにエラーが出たら、そのエラーメッセージをコピーしてCursorに貼り付け、「このJavaScriptのエラーを解決して」と依頼すれば、AIがデバッグを手伝ってくれます。
// JavaScript: callGemini関数の書き換え
const callGemini = async (prompt) => {
// WordPressに新しく作ったAPI窓口のURL
const apiUrl = "/wp-json/my-ai/v1/query";
// WordPressサーバーに送るデータ(シンプル!)
const payload = {
prompt: prompt
};
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`サーバーエラーが発生しました。ステータス: ${response.status}`);
}
const result = await response.json();
// WordPressサーバー経由で受け取ったGoogleからの応答を処理
if (result.candidates && result.candidates.length > 0) {
const text = result.candidates[0].content.parts[0].text;
return text.replace(/\n/g, '<br>').replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
} else {
console.error("予期しないAPI応答:", result);
const errorMessage = result.error ? result.error.message : "AIからの応答が予期しない形式でした。";
return `エラー: ${errorMessage}`;
}
} catch (error) {
console.error("API呼び出しエラー:", error);
return "AIとの通信中にエラーが発生しました。";
}
};
変更のポイント:
const apiKey = ...
の行が完全に削除されました。これで、APIキーはブラウザからは一切見えなくなりました。apiUrl
が、GoogleのURLからあなたのWordPressサイト内のURLに変わりました。fetch
で送信するデータが、単純にプロンプトだけを送る形に変わりました(複雑な処理はすべてPHP側が担当します)。
コメント