簡易的なフォームを自作することがあったのですが、簡易的なフォームとはいえ、スパムが来るのはめんどくさい。。。と思ってreCAPTCHAを導入しました。
普段は問い合わせフォームの自作はほとんどせず、Contact form7やMW WP Formで事足りているので、自作フォームへのreCAPTCHA導入方法は初めてでした。導入した時に調べたことや導入方法をまとめていきます。
Google reCAPTCHA v2のサイトキーとシークレットキーは取得していることを前提として記事を書いていきます。
制作したフォームの特徴
今回制作したフォームの特徴は下記のようなものです。
- formタグを使用せず、POSTはjavascriptで行う。(POST前に何らかの処理をしたい場合もあるので)
- POSTはXMLHttpRequestを使用。
- サーバーサイド側はphpで実装。
- reCAPTCHAの認証が成功したかどうかをjavascript側に返す。
サンプルフォーム
サンプルフォームを作成したので、確認してみてください。
サンプルフォームは送信しても、入力した内容はどこにも送信されません。
送信が成功(reCAPTCHAの認証が成功)したら、「成功しました。」の文字と入力した内容が表示されます。
この時表示される内容は、一応phpから返ってきた内容です。
送信が失敗(reCAPTCHAの認証が失敗)したら、「失敗しました。」と表示されます。
「送信」ボタンを押すたびに、入力内容はリセットされ、reCAPTCHAのチェックもリセットされます。
html
htmlのコードは下記のような感じです。
【サイトキー】には取得したサイトキーを入れます。
www.google.com/recaptcha/api.jsの読み込みを忘れないようにします。
また先に説明しているように、今回はformタグを使用しません。
<div id="form">
<!-- 入力フォームのhtml挿入 -->
<div class="g-recaptcha" data-sitekey="【サイトキー】"></div>
<button id="submit">送信</button>
</div>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
javascript
javascriptのコードは長くなっているので、別ウィンドウで表示するようにしています。
2、3行目はhtmlあった<div id="form">と<button id="submit">になります。
4行目formitemsは<div id="form">内にあるinput,textarea,selectの要素をすべて取得しています。別のフォームが同じページに混在する場合もあるので、<div id="form">内のものを取得するようにしています。
5行目から以下はすべて<button id="submit">をクリックしてからの挙動になります。
POSTするテキストの準備(7〜35行目)
まず最初にPOSTするテキストの準備を進めます。
sendTextObjは一時的に入力された内容を整理するために準備しているオブジェクトです。
sendTextには最終的にPOSTするテキスト入れます。
sendTextObjの準備(9〜25行目)
sendTextObjは先程も説明したように、入力内容を整理するために準備しているオブジェクトで、最終的には下記のような完成を目指しています。
※サンプルフォームでの入力の場合
sendTextObj = {
"名前":["山田太郎"],
"年齢":["20"],
"都道府県":["東京都"],
"性別":["男"],
"スポーツ":["野球","サッカー"],
"SNS":["twitter","facebook"],
"テキストエリア":["入力内容"]
}
「性別」はradio、「スポーツ」「SNS」はcheckboxですが、チェックされた値を取得してセットするような形です。
単純にformitemsをすべて繰り返し値を取得するだけだと、チェックされていない値も取得してしまうので、それを防ぐために記述が長くなってしまっています。
またコード内にも記述がありますが、入力された値に対して、エスケープ処理を行う場合は10行目の「itemValue」に行ってください。
g-recaptcha-responseの準備(26〜31行目)
ここでセットしている値はreCAPTCHAの認証のためにサーバー側で使う値になります。
formitemsを定義したときには、[name="g-recaptcha-response"]の要素はおそらく存在していないので、ここでセットします。
もしformitemsを定義した段階で[name="g-recaptcha-response"]の要素が存在しており、タグがinput,textarea,selectのいずれかだった場合は、sendTextObjの準備(9〜25行目)のときにすでにセットされているので、念の為「!('g-recaptcha-response' in sendTextObj)」という分岐をつけています。
POSTするテキストの準備(31〜35行目)
これまで準備してきたsendTextObjはオブジェクトなので、このままPOSTすることはできません。
そこでsendTextObjをforEachで回して、1つずつsendTextに入れていきます。
最終的にsendTextは以下のようになります。
sendText = '名前=山田太郎&年齢=20&都道府県=東京都&性別=男&スポーツ=野球,サッカー&SNS=twitter,facebook&テキストエリア=入力内容&g-recaptcha-response=****';
ここまででPOSTするテキストは準備できたので、次はphpへPOSTしていきます。
ここでは「send.php」に渡すという想定でいきます。
send.phpへPOST(38〜53行目)
38、39行目はjavascriptで外部ファイルに接続するときによく説明されている記述だと思います。
今回はPOSTなので注意してください。
フォームの内容をPOSTするときは40行目のように「setRequestHeader」を使ってcontent-typeをセットします。
41行目では先程準備した「sendText」をPOSTしています。
POSTした後の処理(42〜53行目)
正常に接続できた時の(xhr.readyState === 4 && xhr.status === 200)で分岐していますが、これも他のページなどでよく見るところだと思います。
44行目で返ってきた値をパースして responseObj に入れています。
あとのphpに書いていますが、これはsend.phpでreCAPTCHAの認証が成功/失敗で返す値を変えています。
reCAPTCHAの認証が成功したときはresponseObj["recaptcha"] = 'SUCCESS'になるようにしています。
reCAPTCHAの認証が失敗したときはresponseObj["recaptcha"] = 'FAILURE'になり、responseObj["result"]にreCAPTCHAのエラー内容が入るようにしています。
reCAPTCHAのリセット(55行目)
55行目のgrecaptcha.reset();では、一度チェックを入れたreCAPTCHAをリセットし、再度チェックを入れ、画像認証をしなければいけないようにしています。
今回のフォームはformタグを使わず、制作しており、画面のリロードはしないので、送信するたびにeCAPTCHAをリセットしています。
php
最後はphp(ここではsend.php)のコードになります。
<?php
$recaptcha = htmlspecialchars($_POST["g-recaptcha-response"],ENT_QUOTES,'UTF-8');
$secretKey = "【シークレットキー】";
$resp = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret={$secretKey}&response={$recaptcha}");
$resp_result = json_decode($resp,true);
if(intval($resp_result["success"]) !== 1) {
// reCAPTCHAの認証が失敗したとき
$array = array(
'recaptcha'=>'FAILURE',
'result'=>$resp_result
);
print_r(json_encode($array));
}else{
// reCAPTCHAの認証が成功したとき
// メール送信などはこちらで
$array = array(
'recaptcha'=>'SUCCESS'
);
print_r(json_encode($array));
}
?>
最初の5行目($resp_result=・・・)まではそのまま使用してください。シークレットキーには取得したキーを入れてください。
コードにはreCAPTCHAの認証が成功/失敗のほうのそれぞれの分岐を書いているので、参考にしてみてください。
またjavascriptのほうでも書いたようにreCAPTCHAの認証が成功/失敗で返す値が異なります。
複数の情報を渡せるように配列で準備し、json_encodeをして値を返しています。
これを利用すれば、入力された内容によってDBにアクセスして、DBから取得した内容をjavascriptに返すといったこともできると思います。
サンプルフォームではこれまで記述したコードに色々付け加えたものなので、ここにある内容を全てコピペしてもサンプルフォームとは同じ挙動にはなりません。
reCAPTCHA v2実装の記事でしたが、他にも色々応用できるところがあると思います。
なにかの参考になれば幸いです。