Webフォームを公開しているけどBotに悩まされている人が読むブログ

2024年1月14日

テクノロジー

今どきWebフォームって・・・考えてみても代替手段があまりない。 未だに企業では、ホームページのお問合せフォームなどを、メールで担当者に送信して対応している。 Woedpressなどのフレームワークでは簡単に設置できるし、メールを送付するのもさほど大変ではない。 でもでもでもでも・・・色々なBotメールが飛んでくる事には変わりない。 変なスパムお問合せだったり、海外からの無意味な文字列の送信だったり、中には嫌がらせや、脆弱性ハッキングみたいなモノもある。 こうした本来の目的ではないフォーム送信をできる限り防ごうと立ち上がってみた。

入力フォームで防止できるゴミ送信

変なスパムお問合せ → 防止 海外からの無意味な文字列の送信 → ある程度防止 嫌がらせ送信 → 無理ゲー 脆弱性ハッキング → 不明
要するに、機会的に入力フォームに自動入力されて、自動で送信ボタンを押されるBOTシステムに対して防御できる方法を今回提案したいと思います。 そして、既存の何かしらのフレームワークなどを使わずに、誰もが便利に簡単に運用も楽になる方法を、お伝えしたいと思います。

Webフォームのベストプラクティス

Web入力フォームって、受け側のサーバーサイドの仕組みを作るのが、まあまあ面倒くさいのは、エンジニアなら誰しも理解している。 単純にデータを受け取ってそれをファイルやデータベースに格納する処理のための環境構築はまあまあ面倒くさい。 受け取り時にエラーが出たら仕事のお問合せの場合は、機会損失につながってしまうので、とんでもない失態となってしまうし、そもそも、個人情報送付なので、データ漏洩などを考慮したセキュリティもしっかりとしないといけない。 Wordpressを使えば良いって? WixやJimdoなどのサービスを使えば良いだって? それぞれの面倒くさいシステム連動しないといけなかったり、環境を合わせないといけなかったり、自分好みのデザインにできなかったり・・・ それらを一気に解消できるのが、GoogleForm対応です。 以前もブログで書いているので、知ってる人もいるかもしれませんね。 以前ブログ: これは便利、CGIを一切使わずにformデータが収集できる、Google form活用方法 この方法で行えば、自分独自で好きなように勝手にデザインした見た目で、HTMLを組み立てることができます。 そして、送信先はGoogleサーバーなので、安定感ハンパないことは言うまでもないでしょう。

Webフォームサンプル

以前ブログで公開した内容を少し効率的に書き直してみました。

contact.html

<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="style.css"/> <script src="main.js"></script> </head> <body> <div class='contact'> <h1>お問い合わせフォーム</h1> <form method='post' name='contact'> <div class='group' data-require="1"> <div class='require'></div> <span class='caption'>何に関するお問い合わせですか?</span> <div class='form'> <div class='inputs'> <select name='entry_type' required> <option value='このサイトの内容について'>このサイトの内容について</option> <option value='誤字脱字の報告'>誤字脱字の報告</option> <option value='技術的な質問'>技術的な質問</option> <option value='その他'>その他</option> </select> </div> </div> </div> <div class='group' data-require="1"> <div class='require'></div> <span class='caption'>名前</span> <div class='form'> <div class='inputs'> <input type='text' name='entry_name' placeholder='名前' required> </div> </div> </div> <div class='group' data-require="1"> <div class='require'></div> <span class='caption'>メールアドレス</span> <div class='form'> <input type='text' name='entry_mail' placeholder='yourname@example.com' required> </div> </div> <div class='group' data-require="1"> <div class='require'></div> <span class='caption'>お問い合わせ内容</span> <div class='form'> <textarea name="entry_memo" data-name='message' placeholder='お問合せ内容を記入ください。' required></textarea> </div> </div> <div class='submit-area'> <button class='submit-button' type='button'>送信する</button> </div> </form> </div> </body> </html>

styke.css

form{ display:block; width:90%; max-width:800px; border-radius:8px; box-shadow:4px 4px 20px rgba(0,0,0,0.3); margin:10px auto; padding: 0 20px; } form > *{ /* margin:20px 10px; */ padding:20px 0; } form > * + *{ border-top:1px solid rgb(200,200,200); } form .group{ display:flex; } form .group > *{ margin:0 10px; } form .group .require{ width:70px; } form .group .require::before{ content:""; display:inline-block; width:60px; padding:5px; border-radius:4px; color:white; text-align:center; font-size:0.8rem; } form .group[data-require="1"] .require::before{ content:"必須"; background-color:red; } form .group[data-require="0"] .require::before{ content:"任意"; background-color:#337ab7; } form .group .caption{ display:inline-block; width:120px; padding:5px 0; white-space:pre-wrap; word-break:break-all; font-size:0.9rem; } form .group .form{ width:calc(100% - 120px - 60px); -webkit-flex-grow: 1; flex-grow: 1; font-size:0; } form .group .form *{ width:100%; font-size:16px; } form .group .form .inputs{ display:flex; } form .group .form .inputs > *{ display:inline-block; --size-input-margin:4px; margin:0 var(--size-input-margin); } form .group .form *::placeholder{ font-size:0.9rem; color:rgb(180,180,180) } form .group .form input[type="text"], form .group .form select, form .group .form textarea{ -webkit-appearance : none; appearance : none; border:1px solid rgb(180,180,180); border-radius:4px; padding:5px; } form .group .form textarea{ height:150px; resize:vertical; } form .submit-area{ text-align:center; } form .submit-area button{ display:inline-block; padding:10px 20px; background-color:#337ab7; border:1px solid #2e6da4; color:white; border-radius:4px; cursor:pointer; } form .submit-area button:hover{ background-color:#2e6da4; } form .group .form .caution{ display:none; color:red; font-size:0.9rem; margin-top:5px; padding:0 5px; } form .group[data-caution='1'] .form .caution{ display:block; } .contact .summary{ width:100%; max-width:500px; margin:20px auto 50px; border:1px dashed rgb(200,200,200); padding:20px; } @media (max-width: 768px){ form{ padding:0 40px; } form .group{ display:block; } form .group > *{ width:100%!important; margin:0; } form .group .caption{ white-space:normal; } } @media (min-width: 600px) and (max-width: 700px){ form .group{ --size-require-width:60px; display:block; } form .group .require, form .group .caption{ display:inline-block; margin-bottom:10px; } form .group .require{ width:var(--size-require-width); } form .group .caption{ width:calc(90% - var(--size-require-width)); white-space:normal; } form .group .form{ width:calc(100% - 20px); } }

main.js

function Main(){ window.submitted = false; this.set_human_check() this.make_iframe() this.set_form() this.set_name() this.set_button() } Main.prototype.thanks_url = "thanks.html" Main.prototype.complete_name = "complete" Main.prototype.preview_url = "https://docs.google.com/forms/d/e/@@@/viewform" // 作成したGoogleFormのプレビューURL // HTMLのname値と、作成したGoogleForm項目のname値を連想配列でデータ化しておく。 Main.prototype.names = { entry_type: "entry.664214585", entry_name: "entry.1570939787", entry_mail: "entry.756098877", entry_memo: "entry.400379748", } Main.prototype.get_form = function(){ return document.forms.contact } Main.prototype.get_button = function(){ return document.querySelector(`button.submit-button`) } Main.prototype.make_iframe = function(){ const iframe = document.createElement("iframe") iframe.id = this.complete_name iframe.name = this.complete_name iframe.style.setProperty("display", "none", "") iframe.onload = this.submitted.bind(this) document.body.appendChild(iframe) } Main.prototype.submitted = function(e){ if(!this.flg_submitted){console.log("error-submit");return} console.log("submit") location.href = this.thanks_url } Main.prototype.set_form = function(){ const url = this.preview_url.replace("viewform", "formResponse") const form = this.get_form() form.setAttribute("action" , url) form.setAttribute("target" , this.complete_name) form.onsubmit = (function(){ if(!this.flg_human_check){return false} this.flg_submitted = true }).bind(this) } Main.prototype.set_name = function(){ const form = this.get_form() for(const key in this.names){ form[key].name = this.names[key] } } Main.prototype.set_button = function(){ const button = this.get_button() if(!button){return} button.type = "submit" } Main.prototype.set_human_check = function(){ this.flg_human_check = false window.addEventListener("keydown", this.push_human_check.bind(this)) } Main.prototype.push_human_check = function(e){ this.flg_human_check = true } switch(document.readyState){ case "complete": case "interactive": new Main() break default: window.addEventListener("DOMContentLoaded" , (()=>new Main())) break } コードが長いのは、申し訳ないですが、かなり自動化できるように書いておきました。 ほぼコピペで対応できると思います。

Botを弾きたい機能

main.jsのプログラムの中で、human_checkという名前が付く変数や関数が書かれているこれが、Bot対応の機能です。 簡単に説明すると、keydownというイベントを使って、入力フォーム内で、一度もキーボードを押していない場合は、送信させないという仕様にしています。 botは、プログラミングで入力フォームに文字を登録して、ボタンをプログラムでクリックしたり、formタグをsubmitする処理をしますが、 送信ボタンをクリックするのも、このsubmitイベントを実行するという事になります。 その時に、keyが押されていないと判断した場合は、処理を停止するというだけの処理をしています。 興味のある人は是非プログラミングコードを分析して読み解いてください。 ※不明な箇所はご質問ください。

あとがき

今回のプログラムソースは、HTML環境だけで実行できるので、サーバーにアップロードしなくてもデバッグできてしまいます。 それだけ簡易に環境依存せずに構築できるし、開発も楽なので、個人的にはこれを使って世の中の企業のホームページなどを構築して差し上げています。 お仕事で作った場合は、GoogleFormの権限を、クライアント担当者に受け渡して、納品完了になりますので、楽ですね。 注意点として、Googleスプレッドシートの機能で、フォームが送信されたらメールを送信する通知設定をするのを忘れないようにお伝えしましょう。 そんなわけで、この機能を今後もっとアップしていくことで、めんどくさいBot対応などを防止していくようにしていきたいと思います。

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。