「ログイン不要・誰でも匿名で投票OK」のサイトは作るのは簡単ですが、結果を信用してもらうのは 驚くほど難しい。 私は1万人以上が登録する投票サイトを運用していて、開設から今までずっと 「便利さ」と「不正耐性」の綱引きをしてきました。この記事では、実際に有効だった防御を 層ごとに紹介します。
大前提: 個人は特定しない、でも行動は区別する
まず大事にしたのがノーログ設計です。IPアドレスをデータベースに保存しない、 アクセスログも残さない。「誰が」は記録しないが「同じ人が連続で」は区別したい — この一見矛盾する要求を満たすのが匿名トークンです。
仕組みは単純で、利用者が最初に利用規約に同意したとき、サーバーがランダムな64文字の トークンを発行し、ブラウザのCookieとlocalStorageに保存します。投票のたびに このトークンが送られ、サーバー側ではトークンのハッシュだけを保存して 「発行済みか」を照合します。IPとは完全に独立なので、個人は特定できず、 「同じブラウザからの連投」だけを検出できます。
第1層: 発行済みトークンだけを受け付ける
最初の穴は「形式さえ正しければ、でっち上げたトークンでも投票できる」ことでした。 ランダムな64文字なんて誰でも生成できます。今は投票・評価・コメントの 全部で「このトークンは実際に発行されたものか」をデータベースで照合しています。 さらに、発行から1時間以内のトークンには別枠の制限をかけています。 これは「規約に同意し直す→新しいトークンで回数リセット」という手口 (発行直後のトークンを使った連投)への対策です。
第2層: レート制限は「速い波」と「じわじわ」の両方を塞ぐ
攻撃の形は大きく2つ。秒単位で連打してくるボットと、人間が暇なときにじわじわ投稿してくる嫌がらせです。 片方だけの対策では足りません。実際に使っている制限の考え方:
- 短時間バースト(1分・10分単位): ボットの連打を止める。 これはメモリ上のカウンタで判定(IPもトークンも保存不要)
- 長時間キャップ(1日・24時間単位): 人間のじわじわ攻撃を止める。 こちらはトークン単位でDB集計
- 対象ごとの制限: 「同じページに対する投稿」だけを細かく制限する。 全体の制限を厳しくすると普通の利用者の行動まで縛ってしまうので、対象×行為の粒度で絞るのがコツ
第3層: 「連投」との戦い — 1ページ制限から横断制限へ
いちばん手こずったのがこれです。投票サイトではコメントも得点に影響する設計にしていたため、1人が大量にコメントするとスコアを自在に操作できてしまう。
まず「同じ相手へのコメントは10分に3件・24時間に5件まで」という制限を入れたところ、 次に相手は「1件ずつ、たくさんのページに小分けにして貼る」という手口に 切り替えてきました。同じ文面のコピペが数十ページに散る。1ページ単位の制限では まったく捕まりません。
そこで入れたのがページ横断の文面チェックです。同じトークンが 24時間以内に「20文字以上の同一文面」を複数ページに投稿したらブロック。 さらに、文面を少し変える回避にも備えて、自分の過去コメントとの類似度(0.9基準)でもブロックします。2文字だけ変えたコピペは 類似度が0.95を超えるので、これで止まります。
第4層: 消すときは「隔離テーブル」に移す
不正が見つかった大量の投稿や投票は、その場で完全に削除するのではなくいったん隔離用のテーブルに移してから消す運用にしています。 理由は2つ。ひとつは誤検知の復元ができること。 もうひとつは、攻撃の分析が後からできることです。 「この波は同じトークン6個で19コピーだった」のような記録が残っていると、 次の対策の精度が上がります。
ちなみに得点は投稿テーブルからリアルタイムに集計する設計なので、 不正分を隔離した瞬間にスコアが自動で元に戻ります。 「不正を消すと、正しい結果が勝手に戻ってくる」構造にしておくのはかなり大事です。
まとめ: 完全防御は無い、だから「コストを上げる」
匿名である以上、トークンを作り直されれば原理的にイタチごっこです。だから目標は「完全に防ぐ」ではなく「攻撃のコストを、得られる効果より高くする」。 トークン照合、二段のレート制限、横断の類似チェック、隔離付き削除 — 個人開発の規模でも、これだけの層があれば「荒らしが割に合わない」状態は作れます。 実際、うちのサイトでは波が来るたびに対策を1枚足す、という進化を続けています。