[実例コードあり PHP+Google reCAPTCHA V3]チャレンジをボタンに自動的にバインドする

PHP

お疲れ様です。すぺきよです。

仕事でGoogle reCHAPTCHA V3を開発中のサイトに実装することがありました。

Googleのガイドを見ながら実装したのですが、多くの言語向けに解説をするためか、解説が部分的で全体像が掴みづらいです。

今回は、PHPに限っての実装例として解説しようと思います。

検証とコード挿入部分以外のHTMLの実装方法は他の言語でも共通なので参考になると思います。

PHPの部分についても比較的シンプルな実装になりますので、他言語でも参考になるでしょう。

解説を全て読むのが面倒だという場合は、スタート地点のコードと、完成のコードの差分(diff)を見ていただくだけでもある程度使い方がわかると思います

では参りましょう。

もしサーバーとの情報のやり取りをajaxやfetchなどで非同期で行なっている場合は「[実例コードあり PHP+Google reCAPTCHA V3]チャレンジをプログラマティックに呼び出す」を参照してください。
最近のモダンなWebアプリではこちらのパターンの方が多いでしょう。

今回の解説の範囲

今回の解説の範囲は、Googleのガイドの「チャレンジをボタンに自動的にバインドする」の実装部分の範囲に絞ります。

サイトキーやシークレットキーの取得手順はあちこちで解説されていますし、それほど難しくないと思うので省略します。

スタート地点

まずは実装するためのスタート地点です。

以下のコードを含むphpファイルをWebサーバー上に配置してください。

簡単なお問い合わせ画面が表示され、送信ボタンをクリックすることでお問合せ内容が表示されるだけの画面になっています。

coutactus.php

<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <form id="contact_form" method="post" action="accepted.php">
        <dl>
            <dt>お名前:</dt>
            <dd><input type="text" name="name" value="" placeholder="山田 太郎"></dd>
        </dl>
        <dl>
            <dt>お問い合わせ内容:</dt>
            <dd><textarea name="inquiry_body" cols="30" rows="10" placeholder="お問い合わせ内容を記入してください。"></textarea></dd>
        </dl>
        <button>送信</button>
    </form>
</body>
</html>

こちらを実装すると、以下のようなシンプルなお問合せ投稿画面になります。

accepted.php

<?php
if(strcmp(strtoupper($_SERVER["REQUEST_METHOD"]),"POST") !== 0)
{
    http_response_code(405);
    exit(0);
}
$name = filter_input(INPUT_POST, "name");
$inquiry_body = filter_input(INPUT_POST, "inquiry_body");
?>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <p>お問い合わせを受け付けました</p>
    <hr>
    <dl>
        <dt>お名前:</dt>
        <dd><?= htmlspecialchars($name, ENT_QUOTES) ?></dd>
    </dl>
    <dl>
        <dt>お問い合わせ内容:</dt>
        <dd><?= nl2br(htmlspecialchars($inquiry_body, ENT_QUOTES)) ?></dd>
    </dl>
    <button type="button" onclick="location.href = 'contactus.php'">戻る</button>
</body>
</html>

こちらを実装し、contactus.phpに値を入力して送信を行うと、以下のように、シンプルなお問合せ受付完了画面が表示されるようになります。(実際はお問合せ内容は保存、送信されていませんので注意してください。)

ステップ1:サイトキーとシークレットキーを定義

contactus.phpとaccepted.phpの両方の一番上に、以下のコードを追加します。

<?php
const RECAPTCHA_V3_SITE_KEY = "6LeXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXrql";
const RECAPTCHA_V3_SECRET_KEY = "6LeXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEiK";
?>

厳密にいうとcontactus.phpにはRECAPTCHA_V3_SECRET_KEYの定義は必要ありません。
使っていない定数が定義されているのが気持ち悪い場合は取り除いていただいても問題ありません。

ステップ2:contuctus.phpにreCAPTCHA V3を設定

まずはcontuctus.phpを書き換えます。

書き換える点は以下の2箇所です。

contactus.php 書き換えるポイント1

Google reCAPTCHA V3のスクリプトの読み込みとボタンクリック時の動作を設定します。

contuctus.phpの<head></head>の間を編集し、以下のように書き換えます。

before:

<head>
</head>

after:

<head>
<script src="https://www.google.com/recaptcha/api.js"></script>
<script>
   function btnSubmit_onclick()
   {
        document.getElementById("contact_form").submit();
   }
</script>
</head>

Googleのガイドではこの部分は「document.getElementById(“demo-form”).submit();」となっています。
ここを自分の環境に合わせ、情報と共にreCAPTCHAのトークンを送信したいフォームのidに合わせる必要があります。
今回はcontactus.phpのformタグのid属性の値に「contact_form」と定義しているので、「demo-form」ではなく、「contact_form」を指定しなければなりません。

contactus.php 書き換えるポイント2

送信ボタンを書き換えます。

現在の<button>タグを以下のように書き換えます。

before:

<button>送信</button>

after:

        <button class="g-recaptcha" data-sitekey="<?= htmlspecialchars(RECAPTCHA_V3_SITE_KEY, ENT_QUOTES) ?>" data-callback='btnSubmit_onclick' data-action='submit'>送信</button>

ここで重要なのは、buttonタグのclass属性に「g-recaptcha」を追加することと、data-callback属性に設定する値です。
data-callback属性に設定する値は、「contactus.php 書き換えるポイント1」で追加したJavascriptの関数名を指定してください。

書き換えが完了すると

書き換えがうまくいくと、下記のように画面右下にGoogle reCAPTCHAの表示が出るようになります。

また、この時点で送信ボタンをクリックしても特に動作に影響はありません。

一度動作確認してみてください。

ただし、accepted.phpで「var_dump($_POST)」をしてみると、「g-recaptcha-response」という見慣れないキーに対し長い文字列が送信されてい流ことが確認できます。

この「g-recaptcha-response」に与えられている値がreCAPTCHAのトークンになります。

ステップ3:accepted.phpで受け取ったトークンを検証する

次に受け取ったトークンの検証を行います。

これをしなければ、ただパラメータが送られてきて、サイトに負荷がかかるだけで意味がありません。

この検証手順はGoogleのガイドの「ユーザーのレスポンスを確認する」を参考にするのですが、こちらも概念のみの説明で、具体的な実装方法はある程度WEBの知識がないと難しいです。

accepted.php 書き換えるポイント1

まずは、accepted.phpの末尾に検証用の関数の定義を追加します。

こちらは、どこでも共通で使えるようにシンプルに実装しています。

</html>
<?php
/**
 * reCAPTCHAユーザートークンのチェックをおこなう
 *
 * @param string|null $token チェックボックスからPOSTされたトークン($_POST[ 'g-recaptcha-response' ])
 * @param string $secret_key Google reCAPTCHA V3で取得したシークレットトークン
 * @return bool true:チェックOK false:チェックNG
 */
function checkReCaptchaUserToken(?string $token, string $secret_key) : bool
{
    if($token === null)
    {
        return false;
    }
    
    $data = ['secret' => $secret_key,'response' =>  $token];
    $context = 
    [
        'http' => 
        [
            'method'  => 'POST',
            'header'  => implode("\r\n", array('Content-Type: application/x-www-form-urlencoded')),
            'content' => http_build_query($data)
        ]
    ];

    $api_response = file_get_contents("https://www.google.com/recaptcha/api/siteverify", false, stream_context_create($context));
    $response = json_decode($api_response);

    return $response->success;
}

accepted.php 書き換えるポイント2

「accepted.php 書き換えるポイント1」で追加定義した関数を呼び出す部分を追記します。

追記するポイントはどこでも良いのですが、今回は「$inquiry_body = filter_input(INPUT_POST, “inquiry_body”);」の直下としました。

そして、認証が通らなかった場合は、「401 Unauthorized」を返し、処理を中断します。

エラー画面を別途用意する場合や、投稿画面に戻したいは「302 Found」でページを切り替えるなど書き換えてください。

before:

$inquiry_body = filter_input(INPUT_POST, "inquiry_body");
?>

after:

$inquiry_body = filter_input(INPUT_POST, "inquiry_body");
$gr_user_token = filter_input(INPUT_POST, "g-recaptcha-response");

if(!checkReCaptchaUserToken($gr_user_token, RECAPTCHA_V3_SECRET_KEY))
{
    http_response_code(401);
    exit(0);
}
?>

書き換えが完了すると

書き換えが完了し、うまく実装できると、問合受付完了画面が今まで通り表示されるだけとなります。

実際には何も動作が変わっていないので、うまく実装できいるかどうもわかりません。

ステップ4:動作確認

ここまでで実装が終わっていますが、画面上の動作はぱっと見何も変わってないので正しく実装できているかわかりません。

まずは「checkReCaptchaUserToken」関数の中の「$response = json_decode($api_response);」で帰ってきている値をvar_dumpでデバッグ出力してみましょう。

うまく行っていれば、以下のように「検証に成功したか」や「検証結果のスコア」などが含まれているはずです。

完成

最後に完成系のコードを提示します。

contuctus.php

<?php
const RECAPTCHA_V3_SITE_KEY = "6LeXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXrql";
const RECAPTCHA_V3_SECRET_KEY = "6LeXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEiK";
?><!DOCTYPE html>
<html>
<head>
<script src="https://www.google.com/recaptcha/api.js"></script>
<script>
   function btnSubmit_onclick()
   {
        document.getElementById("contact_form").submit();
   }
</script>
</head>
<body>
    <form id="contact_form" method="post" action="accepted.php">
        <dl>
            <dt>お名前:</dt>
            <dd><input type="text" name="name" value="" placeholder="山田 太郎"></dd>
        </dl>
        <dl>
            <dt>お問い合わせ内容:</dt>
            <dd><textarea name="inquiry_body" cols="30" rows="10" placeholder="お問い合わせ内容を記入してください。"></textarea></dd>
        </dl>
        <button class="g-recaptcha" data-sitekey="<?= htmlspecialchars(RECAPTCHA_V3_SITE_KEY, ENT_QUOTES) ?>" data-callback='btnSubmit_onclick' data-action='submit'>送信</button>
    </form>
</body>
</html>

accepted.php

<?php
const RECAPTCHA_V3_SITE_KEY = "6LeXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXrql";
const RECAPTCHA_V3_SECRET_KEY = "6LeXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEiK";

if(strcmp(strtoupper($_SERVER["REQUEST_METHOD"]),"POST") !== 0)
{
    http_response_code(405);
    exit(0);
}
$name = filter_input(INPUT_POST, "name");
$inquiry_body = filter_input(INPUT_POST, "inquiry_body");
$gr_user_token = filter_input(INPUT_POST, "g-recaptcha-response");

if(!checkReCaptchaUserToken($gr_user_token, RECAPTCHA_V3_SECRET_KEY))
{
    http_response_code(401);
    exit(0);
}
?>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <p>お問い合わせを受け付けました</p>
    <hr>
    <dl>
        <dt>お名前:</dt>
        <dd><?= htmlspecialchars($name, ENT_QUOTES) ?></dd>
    </dl>
    <dl>
        <dt>お問い合わせ内容:</dt>
        <dd><?= nl2br(htmlspecialchars($inquiry_body, ENT_QUOTES)) ?></dd>
    </dl>
    <button type="button" onclick="location.href = 'contactus.php'">戻る</button>
</body>
</html>
<?php
/**
 * reCAPTCHAユーザートークンのチェックをおこなう
 *
 * @param string|null $token チェックボックスからPOSTされたトークン($_POST[ 'g-recaptcha-response' ])
 * @param string $secret_key Google reCAPTCHA V3で取得したシークレットトークン
 * @return bool true:チェックOK false:チェックNG
 */
function checkReCaptchaUserToken(?string $token, string $secret_key) : bool
{
    if($token === null)
    {
        return false;
    }
    
    $data = ['secret' => $secret_key,'response' =>  $token];
    $context = 
    [
        'http' => 
        [
            'method'  => 'POST',
            'header'  => implode("\r\n", array('Content-Type: application/x-www-form-urlencoded')),
            'content' => http_build_query($data)
        ]
    ];

    $api_response = file_get_contents("https://www.google.com/recaptcha/api/siteverify", false, stream_context_create($context));
    $response = json_decode($api_response);

    return $response->success;
}

最後に

今回の記事は以上です。

JavaScriptのajaxやfetchを使って情報のやり取りをしている場合、今回の実装方法では実現できません。

そんなWebアプリケーション向けに、次は「チャレンジをプログラマティックに呼び出す」の方の記事を書いてみようと思っています。

タイトルとURLをコピーしました