reCAPTCHAを簡単に試してみる(私はロボットではありませんのアレ)
株式会社テイクーワンのT.Tです。
皆様は「reCAPTCHA」という機能をご存じでしょうか?
サイトの登録、応募フォームにある、「私はロボットではありません」というチェックボックスや、画像を選ばせるアレです。ネットを使っているとよく見かけますが、個人的には「ちょっと煩わしいな……」と思うことも多いです。指示通りに画像を選んでいるつもりなのに、何度もやり直しをさせられたり……。セキュアを保つためとは分かっていても、手間に感じてしまいます。
そこで、「仕組みへの理解が深まれば、この煩わしさも少しは解消されるかも」と考え、実際に簡易的な実装を試してみることにしました。
※注記
今回は手軽に試すことを目的としているため、無料版を利用します。無料版と有料版の違いについては、この記事では解説しませんのでご了承ください。
reCAPTCHAとは
bot等を利用した不正アクセスを防ぐためのツール。
bot判定の手段としては、バージョンにより大きく二つに分かれる。
- チャレンジ(画像選択など)ベースのv2
- ユーザー操作のスコアベースのv3
※チャレンジ(ゆがんだ文字を入力)ベースのv1も存在したが、現在は廃止されている。
実装
reCAPTCHAを登録する
・reCAPTCHA管理ページ(https://www.google.com/recaptcha)よりreCAPTCHAを登録。
1. タイプとドメインの設定する
![]()
2. キーが表示される
![]()
画面を実装する
1. ドメインにlocalhostを指定したので、ローカルサーバーを立てる。(今回はnode.jsが入っている環境だったのでexpressを利用)
・ expressサーバーの構成
│ index.js
│ package-lock.json
│ package.json
│
├─node_modules
│
├─v2
│ index.html
│
└─v3
index.html
2. index.jsに処理を検証処理を記載
const express = require('express');
const path = require('path');
const axios = require('axios');
const app = express();
const PORT = 3000;
// v2用の画面
app.use('/v2', express.static(path.join(__dirname, 'v2')));
// v3用の画面
app.use('/v3', express.static(path.join(__dirname, 'v3')));
app.use(express.json());
// v2用のシークレットキー
const SECRET_KEY_V2 = 'SECRET_KEY_V2';
// v3用のシークレットキー
const SECRET_KEY_V3 = 'SECRET_KEY_V3';
app.get('/', (req, res) => {
res.send('');
});
// 検証エンドポイント
app.post('/verify', async (req, res) => {
const { token, type } = req.body;
// タイプ(v2かv3か)によってシークレットキーを切り替え
const secretKey = type === 'v3' ? SECRET_KEY_V3 : SECRET_KEY_V2;
try {
// Google APIへ検証リクエストを送信
const verifyUrl = `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${token}`;
const googleResponse = await axios.post(verifyUrl);
const data = googleResponse.data;
// v3の検証でスコアが0.5以下の場合は、フォールバック用のフラグを付与
if (type === 'v3' && data.score !== undefined && data.score <= 0.5) {
data.fallback = true;
}
res.json(data);
} catch (error) {
// お試しなので特には考慮していない
res.status(500).json({ error: 'エラーが発生しました' });
}
});
// サーバーの起動
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
3. 各index.htmlに処理を記載
・ v2
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>reCAPTCHA v2</title>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body>
<h2>reCAPTCHA v2</h2>
<form id="captcha-form">
<div class="g-recaptcha" data-sitekey="sitekey"></div>
<br>
<button type="button" onclick="submitToken()">検証</button>
</form>
<h3>検証結果(Googleからのレスポンス):</h3>
<pre id="result"></pre>
<script>
function submitToken() {
// reCAPTCHAのトークンを取得
const token = grecaptcha.getResponse();
if (!token) {
alert("チェックされていません");
return;
}
const param = { token: token, type: "v2" };
// トークンを送信して検証
fetch('/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(param)
})
.then(response => response.text())
.then(data => {
// 結果を画面に表示
document.getElementById('result').innerText = data;
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
</body>
</html>
・ v3
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>reCAPTCHA v3</title>
<script src="https://www.google.com/recaptcha/api.js?render=sitekey"></script>
</head>
<body>
<h2>reCAPTCHA v3</h2>
<button type="button" id="v3-btn" onclick="submitV3()">検証</button>
<div id="v2-content" style="display: none;">
<p style="color: red;">低スコアのためv2で再検証</p>
<div id="v2-checkbox" class="g-recaptcha" data-sitekey="sitekey"></div>
<br>
<button type="button" onclick="submitV2()">検証</button>
</div>
<h3>検証結果:</h3>
<pre id="result"></pre>
<script>
// v3検証
function submitV3() {
grecaptcha.ready(() => {
grecaptcha.execute("sitekey", { action: 'submit' }).then(token => {
sendVerify({ token, type: "v3" }, data => {
if (data.fallback) {
// スコアが低ければ、v3ボタンを隠してv2エリアを表示
document.getElementById('v3-btn').style.display = 'none';
document.getElementById('v2-content').style.display = 'block';
grecaptcha.render('v2-checkbox');
}
});
});
});
}
// v2検証
function submitV2() {
const token = grecaptcha.getResponse();
if (!token) return alert("チェックされていません");
sendVerify({ token, type: "v2" });
}
// 送信処理
function sendVerify(param, callback) {
fetch('/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(param)
})
.then(res => res.json())
.then(data => {
document.getElementById('result').innerText = JSON.stringify(data, null, 2);
if (callback) callback(data);
});
}
</script>
</body>
</html>
確認
v2の動作を確認してみる
1. http://localhost:3000/v2/にアクセス
![]()
お馴染みの「私はロボットではありません」が出てきました。
2. 「私はロボットではありません」にチェック、画像を選択する
![]()
3. 検証ボタンをクリック
![]()
無事に検証成功ですね。
v3の動作を確認してみる
1. http://localhost:3000/v3/にアクセス
![]()
v3はアクセスから検証までの「ユーザーの自然な動き」を裏でスコア化して見ているので、お馴染みのチェックボックスや画像選択といったチャレンジ項目はありません。
2. 検証ボタンをクリック
![]()
スコアが0.9。人間判定をもらいました。
次はbot的動作で確認します。botを作るのは手間なので、ブラウザの管理ツールからUser-AgentをGooglebotにしてbot判定させます。
1. 検証ボタンをクリック
![]()
スコアが0.3。閾値の0.5以下なので、v2検証に移行されています。
まとめ
個人的には煩わしい存在だったreCAPTCHAですが、簡易的な実装を通して、その仕組みへの理解を深めることができました。
そして、画像選択を必要としない「v3」があったんですね。普段Webシステムの開発に携わっているものの、これまで実務で導入する機会がなかったため、知りませんでした。
個人的には「画像選択は面倒だから、世の中のサイトは全部v3にしてほしい……」と思ってしまいます。ただv2が選ばれているのにも、きっと明確な理由があるのでしょう。
参考情報
reCAPTCHA 公式サイト
https://cloud.google.com/security/products/recaptcha?hl=ja
reCAPTCHA 公式 webサイトでの実装
https://docs.cloud.google.com/recaptcha/docs/setup-overview-web?hl=ja
