PHPでreCAPTCHA(v2)を使ってみた
reCAPTCHAのバージョン2を某システムに実装することがありました。
実装前に検証を行ったので、内容をまとめてみました。
サイトキーやリクエストキーの取得方法については、こちらの記事を確認してください。
公式ドキュメントはこちらです。
HTMLコードのサンプル
reCAPTCHAを検証するサンプルとして、下記のログインフォームを用意しました。
コード
上記で表示されている画像ですが、HTMLのコードは、このように記載しました。
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-dark bg-primary">
<a class="navbar-brand" href="#">reCapture v2 Test</a>
</nav>
<div class="container mt-3">
<h3>ログインフォーム(reCapture v2 Test)</h3>
<form method="post" action="recaptcha_post.php">
<div class="form-group">
<label for="email">メールアドレス</label>
<input type="email" id="email" name="email" class="form-control" />
</div>
<div class="form-group">
<label for="password">パスワード</label>
<input id="password" type="password" name="password" class="form-control" />
</div>
<button type="submit" class="btn btn-primary">送信</button>
<div class="g-recaptcha mt-3" data-sitekey="[reCaptchaの管理画面で取得した*サイトキー*]" data-callback="callback_recaptcha"></div>
</form>
</div>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<script>
function callback_recaptcha(token) {
console.log(token);
alert("click recaptcha: "+token);
}
</script>
</body>
</html>
解説
bootstrap4を使ってパパッと作ってみました。
ログインフォームで、reCAPTCHAを使う場合を想定しました。
reCAPTCHA固有のタグとスクリプトに注目すると
まず、reCAPTCHAを使用するために、下記のスクリプトを読み込む必要があります。
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
そして、下記のタグを入れておくことで、reCAPTCHAの入力フォームが表示されます。
今回はformタグ配下に入れています。
<div class="g-recaptcha mt-3" data-sitekey="[reCaptchaの管理画面で取得した*サイトキー*]" data-callback="callback_recaptcha"></div>
classのg-recaptcha
は必須です。ない場合は表示されなくなります。
data-sitekey属性(「reCAPTCHAの管理画面で取得したサイトキー」の箇所)には、reCAPTCHAの管理画面で取得したサイトキーを入力します。
data-callback属性の箇所には、チェックを入れたときに実行するjavascriptの関数を記載します。
チェックされると、下記の関数が実行されます。
function callback_recaptcha(token) {
console.log(token);
alert("click recaptcha: "+token);
}
引数でrecaptchaのapiで使用するトークンが取得できるので、alertを使って表示してみました。
今回は下記のようにformタグでrecaptcha_post.php
にPOSTするようにしましたが
<form method="post" action="recaptcha_post.php">
SPAなど、ajaxを使う場合はcallbackされたtokenを使うようにすると良さそうです。
今回はformタグの中にreCAPTCHAのタグを入れているので、フォームが送信された場合には、g-recaptcha-response
という名前でトークンが送信されます。
トークンは1回しか使用できません。2回目以降で同じトークンを使用すると、reCAPTCHA APIからtimeout-or-duplicate
のエラーが返ってきます。
phpでreCAPTCHA(v2) APIを実行するサンプル
postされた時にreCAPTCHA APIにリクエストする検証用のPHPファイルを下記のように作成しました。
コード
<?php
echo "<pre><code>";
var_dump($_POST);
echo "</code></pre>";
//reCAPTCHA APIへのリクエストURL
$url = 'https://www.google.com/recaptcha/api/siteverify';
$secretKey = "[reCaptchaの管理画面で取得した*シークレットキー*]";
$request = [
'secret' => $secretKey,
'response' => $_POST['g-recaptcha-response']
];
$context = [
'http' => array[
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query($request)
]
];
$context = stream_context_create($context);
$apiResponse = file_get_contents($url, false, $context);
// reCAPTCHA APIの返却値出力
echo "<pre><code>";
var_dump($apiResponse);
echo "</code></pre>";
解説
最初に送信されたデータが確認できるように、$_POST
を画面に出力するようにしました。
次にreCAPTCHA APIにリクエストするためのurlをとシークレットキーを定義して、それぞれ値を格納しています。
シークレットキーもreCAPTCHAの管理画面から取得できます。
シークレットキーと、画面から送信されてきたトークンを使用して$request
に配列化して格納しています。
そして、$context
にreCAPTCHA APIにリクエストするための情報を配列として格納しています。
最後にfile_get_contents
関数を使用して、reCAPTCHA APIにリクエストを送信します。
送信した後は結果が表示されるようにvar_dump
関数を使用してみました。
動作確認
フォームが表示された後に、入力してチェックしてみます。
チェックを入れると、このようにトークンが取得できることが確認できました。(javascriptのcallback_recaptcha関数)
何度か試していると、このように悪意のあるアクセスでないかチェックするポップアップが出ます。
フォームを送信すると、それぞれ入力した値と、reCAPTCHAのトークンが表示されました。
reCAPTCHA APIの応答内容を確認する
reCAPTCHA APIから返ってきた値はJSONの文字列になっています。
string(90) "{
"success": true,
"challenge_ts": "2021-01-07T04:30:39Z",
"hostname": "localhost"
}"
それぞれ下記のような内容になるようです。
エラーコードはエラーだった場合についてきます。
項目 | 内容 |
---|---|
success | 成功した場合はtrue/失敗した場合はfalse |
challenge_ts | 実行時のタイムスタンプ |
hostname | reCAPTCHAを実行したホスト名 |
error-codes | エラーコード |
返ってきたステータスで、処理を分岐するには下記のようにjson_decode
関数で配列に戻してから分岐すると良さそうですね。
問題ない場合はsuccessがtrueになります。
$apiResponse = json_decode($apiResponse);
if ($apiResponse->success) {
// TODO:成功時の処理...
} else {
// TODO:失敗時の処理...
}
エラーコードの内容について
公式のドキュメントに書いてありますが、下記の意味になります。
エラーコード | 内容 |
---|---|
missing-input-secret | シークレットパラメータがありません。 |
invalid-input-secret | シークレットパラメータが無効または不正な形式です。 |
missing-input-response | リクエストパラメーターがありません。 |
invalid-input-response | リクエストパラメータが無効であるか、形式が正しくありません。 |
bad-request | リクエストが無効または不正な形式です。 |
timeout-or-duplicate | 応答は無効になりました。古すぎるか、以前に使用されたことがあります。 |
コメント
リキャプチャの実装をよく学ぶことができました。とてもありがたいです。どうもありがとうございました。