中小企業の問い合わせフォームのスパム対策を実装する。Turnstile・reCAPTCHAのサーバー側の検証、ハニーポット、送信元の制限、通知メールを届ける設定
中
この
ここでは、
問い合わせフォームのスパム対策は、層を重ねて、どの層で落ちたかを残す
フォームの
| 層 | 止めやすい |
本物を |
利用者の |
|---|---|---|---|
| ハニーポット |
欄を |
低い |
なし |
| 送信までの |
画面を |
低い |
なし |
| Turnstile・reCAPTCHA の |
ブラウザを |
中くらい |
ほぼなし〜確認の |
| 送信元の |
同じ |
低い |
なし |
| 入力内容の |
URLだらけの |
中くらい | なし |
| メールの |
自動返信を |
なし | なし |
どの
Turnstile・reCAPTCHA は、サーバー側でトークンを検証して初めて効く
Turnstile も
| 項目 | Turnstile |
reCAPTCHA v3 |
|---|---|---|
| 検証の |
https://challenges.cloudflare.com/turnstile/v0/siteverify |
https://www.google.com/recaptcha/api/siteverify |
| 送るもの | 秘密鍵 |
秘密鍵と |
| トークンの |
発行から |
2分 |
| 同じトークンの |
1回しかtimeout-or-duplicate) |
期限内に |
| 判定の |
成功かsuccess) |
0.0〜1.0のscore)。 |
| あわせて |
hostname、action |
hostname、action |
reCAPTCHA は、
Turnstile を
// フォームの受け口(Cloudflare Pages Functions の例)
export async function onRequestPost({ request, env }) {
const form = await request.formData();
const token = form.get('cf-turnstile-response') || '';
const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
body: new URLSearchParams({
secret: env.TURNSTILE_SECRET, // 秘密鍵は環境変数に置き、コードに書かない
response: token,
remoteip: request.headers.get('CF-Connecting-IP') || '',
}),
});
const v = await res.json();
const ok = v.success && v.hostname === 'www.example.co.jp' && v.action === 'contact';
if (!ok) {
await logRejected('turnstile', v['error-codes']); // どの層で落ちたかを残す
return new Response('送信を受け付けられませんでした。時間をおいて再度お試しください。', { status: 400 });
}
// ここから先で、ハニーポット・回数制限・保存・メール送信へ進む
}Turnstile の
トークンの
ハニーポットは、人の目にも読み上げにも出さず、自動入力に埋められない欄にする
ハニーポットは、
<div class="hp" aria-hidden="true">
<label for="hp_company_url">この欄は空のままにしてください</label>
<input id="hp_company_url" name="hp_company_url" type="text"
tabindex="-1" autocomplete="off" value="">
</div>/* display:none だけだと読み飛ばすプログラムがあるため、画面の外へ出す */
.hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }決めて
- 欄の
名前に emailやnameのような、自動入力が 反応しやすい語を 使わない - 値が
入っていた ときは、 画面には 通常の 完了を 返し、 保存と メールの 送信だけを しない (落とした ことを 相手に 知らせない) - あわせて、
画面を 表示した 時刻を 隠し値で 持たせ、 表示から 数秒以内に 送られた ものも 同じ 扱いに する。 下限は、 自動入力で 速く 送る 人を 落とさない 短さに する
送信元の制限は、回数の上限とOriginの確認から始める
同じ
| 確かめる |
実装の例 | 注意 |
|---|---|---|
| 送信の方法 | POST以外は |
受け口の |
| 送り元の |
Origin が |
自分の |
| 回数の上限 | IPアドレスごとに、 |
社内や |
| 本文の |
本文の |
相談内容の |
| 本文の中身 | URLの |
落と |
Cloudflare を
自動返信のメールを、他人宛ての迷惑メールの踏み台にしない
問い合わせフォームの
- 自動返信は、
Turnstile などの 検証、 ハニーポット、 回数制限を 通った あとにだけ送る - 自動返信の
本文に、 相談内容や 名前の 欄に 書かれた 文を そのまま 載せない (受け付けた 日時と、 返信の 目安だけに する) - 同じ宛先への
自動返信は、 短い間に 何通も 送らないよう上限を 付ける - 件名や
宛先のような メールの ヘッダーに、 入力された 値を 入れない。 入れる 場合は 改行を 取り除く - 宛先を
フォームの 隠し項目に 書かない。 宛先は サーバーの 側の 設定だけで 決める
4と
通知メールは自社のドメインから送り、問い合わせた人のアドレスは返信先に入れる
社内への
| ヘッダー | 入れる値 | 理由 |
|---|---|---|
| From | 自社の[email protected]) |
自社の |
| Reply-To | 問い合わせた |
通知メールに |
| To | 社内の |
担当者の |
| 件名 | 固定の |
入力された |
Gmail の
メールが
対策を入れたら、落とした件数と本物の送信を両方確かめる
スパム対策は、
- 層ごとの
件数:ハニーポット、 時間、 Turnstile、 回数制限、 本文の 検査の それぞれで、 何件落としたか - 保留の
中身:URLの 多さで 保留に した 投稿に、 本物の 相談が 混ざっていないか - 実際の
送信:対策の 設定や サイトを 変えたら、 スマホと パソコンから 1件ずつ送り、 社内への 通知と 自動返信の 両方が 届くか
株式会社bundlyzeでは、
参照した公式の資料(読んだ日:2026年10月3日)
- Turnstile の
ドキュメント :siteverify の「Server-side validation」 (Cloudflare) 送り先、 トークンの 有効期間300秒、 1回だけ検証できる 点、 hostname・actionなどの応答 - Turnstile の
ドキュメント :Managed・Non-Interactive・Invisible の「Widgets」 (Cloudflare) 3つの 表示の 型 - Google「reCAPTCHA v3」:点数の
範囲、 既定の 目安0.5、 トークンの 期限2分、 非推奨の 表示 - Google Cloud「reCAPTCHA documentation」:reCAPTCHA が
Google Cloud Fraud Defense の 一部と されている 点 - Cloudflare「Rate limiting rules」:リクエストの
回数の 上限と、 プランに よる 違い - IPA
「安全な :ヘッダーをウェブサイトの 作り方 1.8 メールヘッダ・インジェクション」 固定値に する、 宛先を HTMLで 指定しない、 改行の 除去 - Google
「メール送信者の :すべてのガイドライン」 送信者の 認証の 要件、 From ヘッダーの なりすましの 禁止、 1日5,000件を 超える 送信者の SPF・DKIM・DMARC と 一致の 要件
よくある質問
TurnstileやreCAPTCHAをフォームに貼れば、スパムは止まりますか?
画面に
ハニーポットだけで対策しても大丈夫ですか?
入力欄を
スパム対策で、本物の問い合わせまで落としていないか心配です。
どの