reCAPTCHAを導入する方法
reCAPTCHAを導入する方法を紹介します。
そもそもreCAPTCHAとは?
reCAPTCHAは、Googleが提供しているCAPTCHAサービスです。
CAPTCHAは、ユーザーが人間かボット(コンピューター)かを判断するためのサービスです。
reCAPTCHAの種類
reCAPTCHAには、以下の2種類があります。
-
reCAPTCHA v2は、ユーザーがチェックボックスをクリック、画像の判別を行うことで、人間かBotかを判断します。 そのため、ユーザーエクスペリエンスが悪いですね。
-
reCAPTCHA v3は、v2のようなチェックボックスをクリックすることなく、ユーザーの行動データをもとに人間かBotかを判断することができます。
※ reCAPTCHA v1は、すでにサービス終了しています。
reCAPTCHAを導入する方法
大まかな手順は以下の通りです。
- reCAPTCHAコンソール(Google Cloud Platform)での設定
- サイトにreCAPTCHAを導入
reCAPTCHAコンソール(Google Cloud Platform)での設定
Google Cloud Platformにアクセスします。
- 「使ってみる」をクリックします。
- ラベルやドメインを設定します。
- サイトキーとシークレットキーを取得します。
reCAPTCHA Classic 管理コンソール
以前と管理画面の仕様が変わっており戸惑うかもしれませんが、 従来の reCAPTCHA Classic 管理コンソールにアクセスして設定することも可能です。
reCAPTCHAをサイトで使用できるようにコーディング
reCAPTCHA v3 ガイドのサンプルコードを参考に、サイトにreCAPTCHAを導入します。
reCAPTCHA Classic 管理コンソールの左側メニューから、 「以前の管理コンソール」をクリックすると、従来の管理画面にアクセスすることができます。
PHPをバックエンド使用した場合のサンプルコード
大まかな処理の流れとしては、
- フロントエンドでreCAPTCHAのトークンを取得
- PHPで実装した確認画面でトークンを検証
- 検証に成功した場合、その後の処理を実行
フロントエンド
<head>
<!-- サイトキーを使用して JavaScript API を読み込みます -->
<script src="https://www.google.com/recaptcha/enterprise.js?render=reCAPTCHA_site_key"></script>
</head>
<div class="contactBox">
<form action="mail_web.php" method="post" id="contactform">
<table class="formTable">
<tr>
<th>
<label for="name">お名前</label>
</th>
<td>
<input type="text" name="name" id="name" placeholder="お名前を入力してください" required>
</td>
</tr>
<tr>
<th>
<label for="email">メールアドレス</label>
</th>
<td>
<input type="email" name="email" id="email" placeholder="メールアドレスを入力してください">
</td>
</tr>
<tr>
<th>
<label for="message">お問い合わせ内容</label>
</th>
<td>
<textarea name="message" id="message" placeholder="お問い合わせ内容を入力してください"></textarea>
</td>
</tr>
</table>
<!-- /.formTable -->
<input type="hidden" name="recaptchaToken" id="recaptchaToken">
<input type="submit" value="送信">
</form>
</div>
<!-- /.contactBox -->
<!-- 送信ボタンをクリックした際に、reCAPTCHAのトークンを取得して送信 -->
<script>
document.getElementById('contactform').addEventListener('submit', onClick);
function onClick(e) {
e.preventDefault();
grecaptcha.enterprise.ready(function() {
// トークンを取得
grecaptcha.enterprise.execute('6Le7MhYqAAAAAKs13l-SpAbsvceKruD2-CqY7swM', {
action: 'submit'
}).then(function(token) {
// トークンを送信
var recaptchaToken = document.getElementById('recaptchaToken');
recaptchaToken.value = token;
document.getElementById('contactform').submit();
console.log('✅ 送信完了、トークン:' + token);
});
});
}
</script>
送信先、確認画面をPHPで作成
<?php
require_once '../include/recaptcha_logger.php';
$name = $_POST['name'];
$email = $_POST['email'];
$message = $_POST['message'];
$recaptchaToken = $_POST['recaptchaToken'];
/**
* @var int $sendmail 送信フラグ
*/
$sendmail = 0;
$sendmail = isset($_POST['mail_set']) ? 1 : 0;
?>
<!-- 入力内容の確認 -->
<?php if ($sendmail == 0) { ?>
<?php
$recaptchaLogger = new ReCaptchaLogger();
if (isset($_POST['recaptchaToken'])) {
$verifyResult = $recaptchaLogger->verifyToken($recaptchaToken, $_POST);
if (!$verifyResult->success) {
?>
<div class="input-confirm">
<h2>入力内容の確認</h2>
<p>名前:<?php echo $name; ?></p>
<p>メールアドレス:<?php echo $email; ?></p>
<p>お問い合わせ内容:<?php echo $message; ?></p>
</div>
<p>reCAPTCHAの検証に失敗しました。</p>
<a href="index.php">戻る</a>
<?php
} else {
?>
<div class="input-confirm">
<form action="mail_web.php" method="post">
<h2>入力内容の確認</h2>
<p>名前:<?php echo $name; ?></p>
<p>メールアドレス:<?php echo $email; ?></p>
<p>お問い合わせ内容:<?php echo $message; ?></p>
<input type="hidden" name="name" value="<?php echo $name; ?>">
<input type="hidden" name="email" value="<?php echo $email; ?>">
<input type="hidden" name="message" value="<?php echo $message; ?>">
<input type="hidden" name="recaptchaToken" value="<?php echo $recaptchaToken; ?>">
<input type="hidden" name="mail_set" value="confirm_submit">
<input type="submit" value="送信">
</form>
</div>
<?php }
}
}
if ($sendmail == 1) {
header('Location: thanks.php');
}
reCAPTCHAの検証についての処理をクラスで管理
<?php
class ReCaptchaLogger
{
private $log_dir;
private $secret_key;
public function __construct($secret_key = 'reCAPTCHA_secret_key')
{
$this->log_dir = __DIR__ . '/../logs/recaptcha/';
if (!file_exists($this->log_dir)) {
mkdir($this->log_dir, 0777, true);
}
$this->secret_key = $secret_key;
}
/**
* ログを保存する
* @param array $data ログデータ
*/
private function saveJsonLog($data)
{
try {
$json_file = $this->log_dir . 'recaptcha_' . date('Y-m-d_H-i-s') . '.json';
if (file_put_contents($json_file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)) === false) {
error_log('ファイルに書き込めませんでした: ' . $json_file);
}
} catch (Exception $e) {
error_log('エラーが発生しました: ' . $e->getMessage());
}
}
/**
* CURLエラーをログに記録する
* @param string $error エラーメッセージ
* @param array $post_data 送信データ
*/
private function logCurlError($error, $post_data)
{
$error_data = [
'timestamp' => date('Y-m-d H:i:s'),
'type' => 'recaptcha_curl_error',
'error' => $error,
'post_data' => $post_data,
];
$this->saveJsonLog($error_data);
}
/**
* 検証エラーをログに記録する
* @param array $error_messages エラーメッセージ
* @param array $verify_result 検証結果
* @param array $post_data 送信データ
*/
private function logVerificatonError($error_messages, $verify_result, $post_data)
{
$error_data = [
'timestamp' => date('Y-m-d H:i:s'),
'type' => 'recaptcha_verification_error',
'error_messages' => $error_messages,
'verify_result' => $verify_result,
'post_data' => $post_data,
];
$this->saveJsonLog($error_data);
}
private function getVerificationErrorMessages($error_codes)
{
$error_messages = [];
foreach ($error_codes as $code) {
switch ($code) {
case 'missing-input-secret':
$error_messages[] = 'シークレットキーが指定されていません。';
break;
case 'invalid-input-secret':
$error_messages[] = 'シークレットキーが無効です。';
break;
case 'missing-input-response':
$error_messages[] = 'reCAPTCHAのレスポンスが見つかりません。';
break;
case 'invalid-input-response':
$error_messages[] = 'reCAPTCHAのレスポンスが無効です。';
break;
case 'bad-request':
$error_messages[] = 'リクエストが無効です。';
break;
case 'timeout-or-duplicate':
$error_messages[] = 'レスポンスの有効期限が切れているか、既に使用されています。';
break;
default:
$error_messages[] = '不明なエラーが発生しました。';
}
}
return $error_messages;
}
/**
* トークンを検証する
* @param string $token トークン
* @param array $post_data 送信データ
* @return object 検証結果
*/
public function verifyToken($token, $post_data)
{
$url = 'https://www.google.com/recaptcha/api/siteverify';
$data = [
'secret' => $this->secret_key,
'response' => $token,
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // localhostで証明書の検証を無視
$response = curl_exec($ch);
if (curl_errno($ch)) {
$this->logCurlError(curl_error($ch), $post_data);
curl_close($ch);
return (object) ['success' => false];
}
$verifyResult = json_decode($response);
curl_close($ch);
if (!$verifyResult->success) {
$error_messages = $this->getVerificationErrorMessages($verifyResult->{'error-codes'} ?? []);
$this->logVerificatonError($error_messages, $verifyResult, $post_data);
}
return $verifyResult;
}
}