Help us understand the problem. What is going on with this article?

PHP + Ajax(jQuery) + reCAPTCHA v3 を実装する

reCAPTCHA v3 とは、クローラーやロボットによるアクセスを制限して、無駄にサイトアクセスさせないために設置する認証装置です。

主に、連投されては困るようなコンタクトフォームや、コメント投稿、ユーザー登録などの画面に設置して、そのボタンを押すのが人間なのかどうかを判定します。人間でないと判断した場合は、エラーを出すなどして実行できないようにします。

v2 までは、画像などの認証でしたが、v3からは、ページにおけるユーザーの動きを見て、その判定を自動で行うので、ユーザーにとっては何も行う必要もなく負担をかけることもありません。

Ajax を使って reCAPTCHA 認証する

書いてみると意外と簡単ですが、これを行うには、reCAPTCHA 自体の仕様というか、全体の動きを把握してないとコードに落とし込みにくいと思います。

reCAPTCHAイメージ.png

  1. 認証が必要なページにGoogle指定のJavaScriptファイルを読み込む
  2. サイトキーを渡して、Googleのスクリプトでtokenを生成する
  3. token を submit などのタイミングでPOST送信する
  4. 自分のサイト上で受け取った token をGoogleのURLへ「シークレットキー」と一緒に投げる
  5. 認証可否が返ってくる(success: true, または、score: 0〜1など)
  6. その認証可否を参考にしてサーバ上で処理分けする

以上が、だいたいの流れになります。

これを理解した上で、順にコードに落とし込んでいきます。

「サイトキー」と「シークレットキー」を取得する

reCAPTCHA を使用するには、Googleアカウントが必要です。ログインした上で、それぞれのキーを取得するには、以下にアクセスします。

https://g.co/recaptcha/v3

key.png

これらのキーはいわゆる公開鍵のようなイメージでしょうか。その名のとおり「サイトキー」は、HTML 上で確認できるように記述します(ユーザーに見える必要はないですが、覗こうと思えば覗けるという意味です)。その一方で「シークレットキー」は、秘密鍵として自身のサーバ上に記述します。

認証が必要なページに Google 指定の JavaScript ファイルを読み込む

公式ドキュメント
https://developers.google.com/recaptcha/docs/v3

にもありますが、必要なページの「</body>」タグの手前付近で、Google指定のJavaScriptファイルを記述します。

<script src="https://www.google.com/recaptcha/api.js?render=サイトキー"></script>

サイトキーを渡して、Google のスクリプトで token を生成する

次に、あらかじめGoogleスクリプトによって、token を生成してどこかに保持しておく必要があります。

もし <form>タグを作っているようなら、

<input type="hidden" name="recaptcha_response" id="recaptchaResponse">

と、hidden 属性の input タグに埋め込んでしまっても良いでしょう。HTML ファイルが読み込まれると同時に、token を生成して、このタグに格納します。

$(document).ready(function() {
  grecaptcha.ready(function () {
    grecaptcha.execute('サイトキー', { action: 'contact' }).then(function (token) {
        var recaptchaResponse = $('#recaptchaResponse');
        recaptchaResponse.value = token;
    });
  });
});

ただ、今回は、Ajax を使って POST するので、グローバル変数に格納することで留めておきましょう。

// グローバル変数
var tokenData;

$(document).ready(function() {
  // Google reCAPTCHA v3 のトークンを生成
  grecaptcha.ready(function () {
    grecaptcha.execute('サイトキー', { action: 'submit' }).then(function (token) {
      tokenData = token;
    });
  });
});

なお、action に入力される文字列ですが、ここは書き分けることで、admin consoleから「リクエスト数」として集計・参照することができます。ですので、あまり集計や、後の参照を気にする必要がない場合は、文字列はなんであっても良いということです。

token を submit などのタイミングで POST 送信する

そして、上記で保存したグローバル変数を Ajax の POST 値に載せて送信します。

function sendData() {
  $.ajax({
    type: "POST",
    url: "register_submit.php",
    timeout: 10000,
    cache: false,
    data: {
      'mail': "m@hibara.org",
      'password': "password123",
      'recaptchaResponse': tokenData
    },
    dataType: 'text'
  })
  .done(function (data) {
    // "OK"
    location.href = "register_complete.php";
    return true;
  })
  .fail(function () {
    alert("サーバー内でエラーがあったか、サーバーから応答がありませんでした。");
    return false;
  })
  .always(function (data_or_jqXHR, textStatus, jqXHR_or_errorThrown) {
  });
}

自分のサイト上で受け取った token を Google の URL へ「シークレットキー」と一緒に投げる

投げた先、今回上記の例で言うと register_submit.php の中身はこう書きます。

if(isset($_POST['recaptchaResponse']) && !empty($_POST['recaptchaResponse'])) {
  $secret = 'シークレットキー';
  //get verify response data
  $verifyResponse = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='.$secret.'&response='.$_POST['recaptchaResponse']);
  $responseData = json_decode($verifyResponse);

  if ( $responseData->success ) {
    if ( $responseData->score < 0.8 ) { // おまえ人間じゃねえ!
      // 認証スコアが低い
      exit();
    }
  }
  else {
    // 認証失敗
    exit();
  }
}
else {
  // POST値が正常に投げられてこなかった
  exit();
}

// ここまで抜けてきたとき、正常処理を行う

ここのスコア $responseData->score は、0〜1 の間を取ります。1に近いほど人間と判断できます。私が何回か試行してみた実感としては、ほぼ 0.9以上のスコアを叩くような気がします。この値については、サイトによって試行錯誤する必要があるかもしれません。

ここで得られる他の値、結果については、先に挙げた公式ドキュメントにあるとおりです。

{
  "success": true|false,      // whether this request was a valid reCAPTCHA token for your site
  "score": number             // the score for this request (0.0 - 1.0)
  "action": string            // the action name for this request (important to verify)
  "challenge_ts": timestamp,  // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
  "hostname": string,         // the hostname of the site where the reCAPTCHA was solved
  "error-codes": [...]        // optional
}

JSONでの例になっていますが、この仕様に沿って、上記のPHPファイル内で処理すれば問題なく取得できるでしょう。

まとめ

とりあえず、Ajaxで reCAPTCHA v3 を実装した例が少なく、仕様全体のイメージもしにくかったので記事にしてみました。「最初にトークンを生成して投げる」ということを知れば、あとはいくらでも応用が利くとは思います。

hibara
主にWindowsフリーソフトで、「アタッシェケース」という暗号化ツール、Markdown記法に対応した「MarkDown#Editor」などを開発・公開しています。
https://hibara.org/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away