reCAPTCHA
Nuxt.jsで作った問い合わせフォームにbot対策でgoogle reCAPTCHA Version 3を導入しました。v3 は機械学習により読みにく文字を入力したりあいまいな画像選択させたりすることなしで、人間かbotかの判定を0(bot)から1(botではない)のスコアにして返してくれるそうな。
Google reCAPTCHA
https://developers.google.com/recaptcha/?hl=ja
はじめに
利用にはGoogle アカウントにてログイン、サイトを登録、ドメインの登録(本番環境と開発環境、localhostなど)して、サイトキーとシークレットキーを取得します。
APIの読み込み
reCAPTCHを利用するページでapiソースをパラメータに取得したサイトキーを指定して読み込み。nuxt.config.jsで読み込んでも構いません。その場合は全ページにreCAPCHAのラベルが表示されます。
export default {
head() {
return {
script: [
{
src: 'https://www.google.com/recaptcha/api.js?render=サイトキー'
}
],
}
}
}
トークンの取得
フォームを送信する前に、 バックエンド( 今回はphp)のメール送信プログラムにパラメータとして送るトークンを取得します。
methods: {
getToken: function(e) {
grecaptcha.ready(function() {
grecaptcha
.execute('サイトキー', {
action: 'homepage'
})
.then(function(token) {
_this.sendForm(token)
})
})
}
}
バックエンド(php)にパラメーターを渡す
パラメーターは URLSearchParmasインターフェースで渡す必要があるようでけっこうハマりました。先ほど取得したトークンはg-recaptcha-response というパラメータ名でURLSearchParamsに追加、postでバックエンドへ。
methods: {
async sendForm(token) {
const params = new URLSearchParams()
params.append('form-name', this.formName)
params.append('form-email', this.formEmail)
params.append('form-content', this.formContent)
params.append('g-recaptcha-response', token)
const res = await this.$axios.$post(
'https://メール送信プログラムURL',
params
)
...
}
}
バッグエンドでの処理
今回はphpにてメール送信をしました。
header('Access-Control-Allow-Origin: 許可するドメイン');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept');
header('Content-Type: application/json; charset=utf-8' ) ;
サーバーサイドによってアクセス制御を整えます。
シークレットキーとトークン
シークレットキーとトークンをパラメータにgoogle reCAPTCHにお伺いを立てます。その結果がjsonで返ってきます。閾値を設定し、それ以上なら送信、以下ならエラーをnuxtに返します。
// シークレット}キー
$secret_key = 'シークレットキー';
// エンドポイント
$endpoint = 'https://www.google.com/recaptcha/api/siteverify';
$params = '?secret=' . $secret_key;
$params .= '&response=' . $_POST['g-recaptcha-response'];
// botの閾値
$safescore = 0.1;
// 判定結果の取得
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $endpoint . $params);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // curl_execの結果を文字列で返す
curl_setopt($curl, CURLOPT_TIMEOUT, 5); // タイムアウトの秒数
$json = curl_exec($curl);
curl_close($curl);
$obj = json_decode($json);
// 判定の成功と、スコアが閾値以上ならメール送信
if ($obj->success && $obj->score >= $safescore) {
// メール送信
} else {
//閾値以下ならnuxtにjsonを返す
$obj->isbot = true;
echo json_encode($obj);
exit();
}
このスコアは機械学習によるものだそうで、実際に運用してreCAPTCHAのコンソールで確認しならがら閾値を調整していくイメージでしょうか。
実際インド旅行中にホテルのwifiでのテストでは0.5-0.7くらいでしたがスタバのフリーwifiからだと0.1だったり、日本の自宅wifiからだと0.9だったりしました。
これで一通りの実装の流れは終了です。
bot判定だった場合
実際にbotだった場合はさておいて、人間だったけどbot 判定されて送信でき叶った場合にどういうメッセージを表示するのがいいか考えた。
表示する文言はさておいて、別のな方法で問い合わせしてもらう(メールや電話)のもひとつだが、入力した問い合わせ内容(名前、メールアドレス、問い合わせ内容など)を再度入力させるのは申し訳ないので、送れなかった場合はlocalStorageに各項目保存するようにした。これで後ほど違うwifi環境などで試してみたら遅れればlocalStrorageを削除する。
// 判定は成功で、bot判定なら
if (res.success && res.isbot) {
// add localStorage
localStorage.setItem('formName', this.formName)
localStorage.setItem('formEmail', this.formEmail)
localStorage.setItem('formContent', this.formContent)
} else {
// 送信できた場合、localStorageがあれば削除
if(localStorage.getItem('formName')) localStorage.removeItem('formName')
if(localStorage.getItem('formEmail')) localStorage.removeItem('formEmail')
if(localStorage.getItem('formContent')) localStorage.removeItem('formContent')
}
ページリロード時、 localStorageにデータがある場合はそれをinputに渡す
data: function() {
return {
...
// localStorage.getItem('formName') に保存されていればそこから取得
formName: localStorage.getItem('formName') || '',
formEmail: localStorage.getItem('formEmail') || '',
formContent: localStorage.getItem('formContent') || ''
}
},
デスクトップの場合違うwifiで、というのはあまりできないだろうが、せっかく書いた問い合わせ内容が消えてしまうよりはいいだろう。同じブラウザでもう一度試したり、コピペしてブラウザを変えたりメールやメモに移したりもできる。
以上、参考になれば幸いです。