はじめに
この記事は以下の人向けの資料となります。
- 新しくウェブサイトを構築している方
- Google reCAPTCHAのクラシック版を使っている方
クラシック版管理画面
クラシック版についての説明記事
より
もともと「reCAPTCHA」と呼ばれていたサービスです。v2 / v3のバージョンで機能が違います。
昔から(2020年以前まで?)無料でreCAPTCHAを使っている場合は、たぶんこれです。
のようです。
最近自社サービスに「Google reCAPTCHA Enterprise版」を実装したのでそのやり方を書きます。
この記事で取り上げているのはv2版(難易度中)です。
v3版(難易度高)とはViewの実装が少し異なるかもしれません。
サーバー側のトークン検証に関してはv2とv3で違いはない認識です。
※当記事では、トークンの有効性のチェックのみでリスク検証については紹介をしておりません。
前提
Google Cloud Platformのお支払い方法登録を事前に完了させておく必要があります。
実装手順の紹介
APIキーの設定編
手順1.Google Cloud Platformのコンソールを開く
手順2.検索窓から「APIとサービス」を選択
手順3.左メニューから「認証情報」を選択します
手順4.「認証方法を作成」を選択
※赤い四角の部分は自社サービスに関わる部分なのでマスキングしてあります。
手順5.「APIキー」を選択
手順6.APIキーの設定をして保存
■必須
- 名前
- APIの制限
- reCAPTCHA Enterprise API
■任意
- アプリケーションの制限の設定
APIキーの設定編は以上で終了です。
続いて、Google reCAPTCHA Enterprise編に移ります。
Goolge reCAPTCHA Enterpriseの設定編
手順1.検索窓から「reCAPTCHA」を選択
手順2.「キーを作成」を選択
手順3.reCAPTCHAの設定を保存する
■必須
- 表示名
- プラットフォームの種類を選択
- ドメインリスト
- キーのタイプ
- チェックボックスによる本人確認を使用する
- 難易度中 or 難易度高
- チェックボックスによる本人確認を使用する
Goolge reCAPTCHA Enterpriseの設定編は以上で終了です。
続いて、実装編に移ります。
実装編
手順1.システム開発を開く
手順2.「ウェブサイトに reCAPTCHA を追加する」の指示に従ってHTMLにスクリプトを埋め込む
■下記をhead部に記載します
※QUERY STRINGとして「hl」を指定することができます。
hl=jaとした場合、日本語
hl=enとした場合、英語
<script src="https://www.google.com/recaptcha/enterprise.js" async></script>
<script src="https://www.google.com/recaptcha/enterprise.js?hl=ja" async></script>
■reCAPTCHAを表示させたいところに、以下を記載します
※サンプルとしては下記が記載してありますが、カスタマイズすることもできます
<サンプル版>
<div class="g-recaptcha" data-sitekey="YOUR SITE KEY" data-action="LOGIN"></div>
<カスタマイズ版>
<div class="g-recaptcha" data-callback="callbackGoogleRecaptcha" data-expired-callback="callbackGoogleRecaptchaExpired" data-sitekey="YOUR SITE KEY"></div>
<input type="hidden" id="google_recaptcha_code" value="" />
手順3.callbackを定義(任意)
Google reCAPTCHAの認証が成功するまで送信するボタンを押させたくないので、callbackを定義しています。
$(document).ready(function(){
// TODO 送信するボタンを押せないように制御します
});
/**
* Google reCAPTCHAでチェックがついた場合のコールバックです
*
* @param code
*/
function callbackGoogleRecaptcha(code) {
if (code !== '') {
// TODO トークンを取得できたら、送信するボタンを押せるようにします。
// トークンをhiddenでおいておく。これはフラグでもいい。
$('#google_recaptcha_code').val(code);
}
}
/**
* Google reCAPTCHAでタイムアウトした場合のコールバックです
*/
function callbackGoogleRecaptchaExpired() {
// TODO 送信するボタンを押せないように制御します
// トークンを削除する
$('#google_recaptcha_code').val('');
}
/**
* Google reCAPTCHAで次へボタンをクリックしたときの処理です
* onClickに使用します
*/
function clickNextButton() {
// トークンがない場合は送信をしない
if ($('#google_recaptcha_code').val() === '') return;
// 送信を発火させます
form.submit();
}
上記JavaScriptによる制御のサンプル
■未認証時
■認証完了後
■時間経過後
手順4.サーバーで検証をする
今回は「REST API」による検証となります。
■検証プログラム
class Google_recaptcha
{
private const ENTERPRISE_BASE_URL = 'https://recaptchaenterprise.googleapis.com/v1/projects';
public function __construct(private array $options)
{
}
/**
* @return string
*/
private function get_enterprise_project_url(): string
{
return sprintf('%s/%s', self::ENTERPRISE_BASE_URL, $this->options['google_recaptcha_project_name']);
}
/**
* 利用方法が共通な可能性があるので、メソッドとして切り出す
*
* @param string $request
* @param string $method
*
* @return array
*/
private function call_google_recaptcha_enterprise(string $request, string $method): array
{
$enterprise_project_url = $this->get_enterprise_project_url();
$request_url = sprintf('%s/%s', $enterprise_project_url, $method);
$query = [
'key' => $this->options['google_recaptcha_api_key'],
];
$url = sprintf('%s?%s', $request_url, http_build_query($query));
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($request),
]);
// 処理タイムアウト
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$output = curl_exec($ch);
curl_close($ch);
// レスポンスが返ってこないときは空配列とする
if (empty($output)) {
return [];
}
return json_decode($output, true);
}
/**
* @param string $token
*
* @return array
*/
public function assessments(string $token): array
{
$body = [
'event' => [
'token' => $token,
'expectedAction' => '',
'siteKey' => $this->options['google_recaptcha_site_key'],
]
];
$jsonBody = json_encode($body);
return $this->call_google_recaptcha_enterprise($jsonBody, 'assessments');
}
/**
* @param string $token
*
* @return bool
*/
public function validate(string $token): bool
{
$response = $this->assessments($token);
// レスポンスが返ってこないときはエラー
if (empty($response)) {
return false;
}
// トークンの妥当性はこのプロパティで判断
return $response['tokenProperties']['valid'] ?? false;
}
}
検証するときは
// フレームワークごとにリクエストの取得方法は変更する
$token = $_POST['g-recaptcha-response'] ?? '';
// Google_recaptchaをインスタンス化する
$googleRecaptcha = new Google_recaptcha([
'google_recaptcha_api_key' => getenv('GOOGLE_RECAPTCHA_API_KEY'),
'google_recaptcha_site_key' => getenv('GOOGLE_RECAPTCHA_SITE_KEY'),
'google_recaptcha_project_name' => getenv('GOOGLE_RECAPTCHA_PROJECT_NAME'),
]);
// 成功:true 失敗:false
$result = googleRecaptcha->validate($token);
とします。
※何も手を加えていなければ、Google reCAPTCHAのトークンは「g-recaptcha-response」という名前でリクエストがされるかなと思われます。
手順5.Configを定義
envファイルを作成
GOOGLE_RECAPTCHA_API_KEY=YOUR API KEY=fugafuga
GOOGLE_RECAPTCHA_SITE_KEY=YOUR SITE KEY=hogehoge
GOOGLE_RECAPTCHA_PROJECT_NAME=YOUR PROJECT_NAME=hogefuga
完成
2024.12.03現在返ってくるレスポンス
OK時のレスポンス
{
"name": "projects/xxxxxxxxxxxx/assessments/yyyyyyyyyyyyyy",
"event": {
"token": "aaaaaaaaaaaaaaaaaaaaaaaaaaa",
"siteKey": "hogehoge",
"userAgent": "",
"userIpAddress": "",
"expectedAction": "",
"hashedAccountId": "",
"express": false,
"requestedUri": "",
"wafTokenAssessment": false,
"ja3": "",
"headers": [],
"firewallPolicyEvaluation": false,
"fraudPrevention": "FRAUD_PREVENTION_UNSPECIFIED"
},
"riskAnalysis": {
"score": 1,
"reasons": [],
"extendedVerdictReasons": [],
"challenge": "CHALLENGE_UNSPECIFIED"
},
"tokenProperties": {
"valid": true,
"invalidReason": "INVALID_REASON_UNSPECIFIED",
"hostname": "localhost",
"androidPackageName": "",
"iosBundleId": "",
"action": "",
"createTime": "2024-12-03T04:52:23.582Z"
},
"accountDefenderAssessment": {
"labels": []
}
}
NGレスポンス
{
"name": "projects/xxxxxxxxxxxx/assessments/yyyyyyyyyyyyyy",
"event": {
"token": "bbbbbbbbbbbbbbbbbbbbbbbbbbb",
"siteKey": "hogehoge",
"userAgent": "",
"userIpAddress": "",
"expectedAction": "",
"hashedAccountId": "",
"express": false,
"requestedUri": "",
"wafTokenAssessment": false,
"ja3": "",
"headers": [],
"firewallPolicyEvaluation": false,
"fraudPrevention": "FRAUD_PREVENTION_UNSPECIFIED"
},
"riskAnalysis": {
"score": 0,
"reasons": [],
"extendedVerdictReasons": [],
"challenge": "CHALLENGE_UNSPECIFIED"
},
"tokenProperties": {
"valid": false,
"invalidReason": "MALFORMED",
"hostname": "",
"androidPackageName": "",
"iosBundleId": "",
"action": "",
"createTime": "1970-01-01T00:00:00Z"
}
}
さいごに
今回は、Goolge reCAPTCHA Enterpriseの実装をしてみました。
トークンの検証方法は
他にも
- C#
- Go
- Java
- Node.js
- PHP
- Python
- Ruby
の言語が紹介されているのですが、
こちらは以下の理由により個人的におすすめしません。
REST APIはライブラリに依存しないため容易に実装ができます。
■理由
- どのライブラリのバージョンを使った実装サンプルなのか不明
- ライブラリ依存関係の解決が大変
以上、参考にしてもらえればと思います。