長生村本郷Engineers'Blog

千葉県長生村本郷育ちのエンジニアが書いているブログ

Rails に reCAPTCHA v3 導入して bot 対策

概要

Rails で構築した Webサービスbot 攻撃を定期的に受けた為、問い合わせフォームに reCAPTCHA v3 を導入しました。

何故 v2 でなく、reCAPTCHA v3 ?

v2 は I'm not a robot チェックボックスにチェックを入れた後に画像選択させる仕様があります。

例えば、看板が写ってるのはどれ?と選ばせる問いが出てきた場合、
「どこまでが看板としたらいいの?」と心理的負担も高く、ユーザが離脱する可能性もあります。

f:id:kenzo0107:20190216210737p:plain

v3 だと嬉しいことは何?

v3 *1 は設置したページのユーザ行動をスコア化し bot か判断します。

アクセスが増えるとより精度が高まってくる、という仕様です。

bot ユーザへの負担は全くなく、 bot を遮断できる様になるという、世の中進んでるなぁ感満載です。

gigazine.net

gem ある?

今回 gem は使用しませんでした。

というのも、 以下理由からでした。

  • gem 'recaptcha' が v3 非対応。
  • gem 'new_google_recaptcha' は v3 対応してますが、スコアが返ってこないのでテストし辛い。

その他に既にあるのかもわかりませんが、記事執筆時には探し出すことはできませんでした。

まず reCAPTCHA v3 発行

以下 reCAPTCHA コンソールにアクセスし発行してください。

https://g.co/recaptcha/v3

v3 を選択し、今回導入するドメインを登録します。*2

f:id:kenzo0107:20190216214351p:plain

発行されたサイトキー・シークレットキーを保存しておきます。

  • サイトキー

    • ユーザがサイトにアクセスした際にトークンを取得する際に必要なキーです。こちらはユーザ公開して問題ありません。
  • シークレットキー

    • トークンを元に Google に問い合わせする際に必要なキーです。こちらは秘密情報として扱います。

f:id:kenzo0107:20190216214558p:plain

Rails 側実装

Rails >= 5.2 を想定しています。

config/credentials.yml.enc

recaptcha:
  secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

シークレットを秘密情報に保存します。

app/controllers/application_controller.rb

require 'net/http'
require 'uri'

class ApplicationController < ActionController::Base
...
  RECAPTCHA_MINIMUM_SCORE = 0.5
  RECAPTCHA_ACTION = 'homepage'
...
  def verify_recaptcha?(token)
    secret_key = Rails.application.credentials.recaptcha[:secret_key]
    uri = URI.parse("https://www.google.com/recaptcha/api/siteverify?secret=#{secret_key}&response=#{token}")
    r = Net::HTTP.get_response(uri)
    j = JSON.parse(r.body)
    j['success'] && j['score'] > RECAPTCHA_MINIMUM_SCORE && j['action'] == RECAPTCHA_ACTION
  end
end

共通メソッドとして、recaptcha の認証メソッド verify_recaptcha? を設定しています。

ここで、bot となるスコアを 0.5 以下としています。

通常通り操作していれば、十分超える数値です。

config/locales/en.yml

en:
  recaptcha:
    errors:
      verification_failed: 'reCAPTCHA Authorization Failed. Please try again later.'

local en 設定です。

config/locales/ja.yml

ja:
  recaptcha:
    errors:
      verification_failed: 'reCAPTCHA 認証失敗しました。しばらくしてからもう一度お試しください。'

local ja 設定です。

app/controllers/hoges_controller.rb

class HogesController < ApplicationController
  def new; end

  def create
    unless verify_recaptcha?(params[:recaptcha_token])
      flash.now[:recaptcha_error] = I18n.t('recaptcha.errors.verification_failed')
      return render action: :new
    end

    # something to do

    redirect_to hoge_finish_path
  end

  def finish; end
end

new から create に post して reCAPTCHA で bot 判定して

  • OK → finish へ進む
  • NG → new に戻る

という設計です。

app/views/hoges/new.html.erb

<% if flash[:recaptcha_error] %>
<div class="text">
  <p><spacn class="error"><%= flash[:recaptcha_error] %></span></p>
</div>
<% end %>

<%= form_tag({action: :create}, {method: :post}) do %>
...
  <input id="recaptcha_token" name="recaptcha_token" type="hidden"/>
  <%= submit_tag "送信する", :class => "submit-recaptcha btn", :disabled => true %>
<% end %>

<script src="https://www.google.com/recaptcha/api.js?render=<%= Settings.recaptcha.site_key %>&ver=3.0"></script>
<script>
grecaptcha.ready(function() {
  grecaptcha.execute('<%= Settings.recaptcha.site_key %>', {action: 'homepage'}).then(function(token) {
    $('#recaptcha_token').val(token);
    $('.submit-recaptcha').prop('disabled', false);
  });
});
</script>
エラーメッセージ表示
<% if flash[:recaptcha_error] %>
<div class="text">
  <p><spacn class="error"><%= flash[:recaptcha_error] %></span></p>
</div>
<% end %>
<form> ~ </form> 内に以下 name=recaptcha_token input タグを追加します。
<input id="recaptcha_token" name="recaptcha_token" type="hidden"/>
ページアクセス時に reCAPTCHA の token を取得すべく、スクリプトを仕込みます。
<script src="https://www.google.com/recaptcha/api.js?render=<%= Settings.recaptcha.site_key %>&ver=3.0"></script>
<script>
grecaptcha.ready(function() {
  grecaptcha.execute('<%= Settings.recaptcha.site_key %>', {action: 'homepage'}).then(function(token) {
    $('#recaptcha_token').val(token);
    $('.submit-recaptcha').prop('disabled', false);
  });
});
</script>

reCAPTCHA トークン取得が成功した場合に以下実行します。

  • id="recaptcha_token" input タグの valueトークンを設定
  • submit ボタンの有効化

<%= Settings.recaptcha.site_key %> について
gem 'settingslogic' をインストールしている前提で設定しています。

導入していない場合は、簡易的に処理を試す程度であれば、 <%= Settings.recaptcha.site_key %> を取得したサイトキーに置き換えて下さい。*3

以上で設定は完了です。

ページにアクセスしてみる

ページ右下に reCAPTCHA マークが常に表示される様になります。

f:id:kenzo0107:20190216234742p:plain

集計情報を見る

reCAPTCHA コンソールを見ると、以下の様な表示が出ていてすぐには集計情報が反映されていないと思います。

f:id:kenzo0107:20190216235044p:plain

しばらく経つと以下の様なグラフが表示される様になります。

f:id:kenzo0107:20190216235204p:plain

注意

例えば、社内 IP 等固定された IP からテストで頻繁にアクセスすると、 bot 扱いされます。

reCAPTCHA 側で IP のホワイトリストはないので、その場合、 Rails 側で許可 IP リストを作る必要があります。

以上
参考になれば幸いです。

*1:2019年2月現在最新バージョン

*2:ドメインは複数登録可能です。ドメイン毎に集計や、 bot 対策の傾向を変えたい場合は、個々に発行します。 また、 RAILS_ENV = production とそれ以外で発行する方が本番への影響がないので推奨されます。

*3:前にもお伝えしましたが、サイトキーの管理は直指定でなく、何かしら管理が推奨です。