私はロボットではありません reCAPTCHA v3設置(Go)


最近管理しているサーバーに不正なアクセスが多発していて、
お問合せフォームに最終データをPOSTしてきやがるBOTが多い事多い事。

という事で最終チェックにGoogleの提供しているreCAPTCHAを取り入れる事にしました。
v2はチェックつけるタイプでv3はなんと何もせずにスコア値で評価されるとの事です。

設置の流れ
* KeyとSecretを取得
* javascriptを設置してスコアを収集(トークンに隠蔽されます)しフォーム送信
* バックエンドでGoogleのAPIにトークンを問い合わせてスコアを取得

こんな感じです。詳しくは公式ドキュメントに。
PHP版は書いている方がいたので(GoogleのreCAPTCHA v3をPHPで使う
今回はバックエンド部分をGoでさくっとやってみます。

githubに全ソース置きました
キーさえ取ればdocker-composeで即動かせます。

1.KeyとSecretを取得

Googleアカウントが必要です。
https://www.google.com/recaptcha/にアクセスし、Admin Consoleからサイトキー、サイトシークレットを取得します。

タイプはv3を。ローカルでテストする場合ドメインには0.0.0.0localhost等を入れる必要があります。

2.フロントにjavascriptとinputフィールドを追加

{{YOUR_SITE_KEY}}は取得したキーを置き換えてください。githubのソースではgoのtemplateを使ってます。

<!-- reCAPTCHA -->
<script src="https://www.google.com/recaptcha/api.js?render={{YOUR_SITE_KEY}}"></script>
<script>
  var recaptcha = document.getElementsByName('recaptcha_token');
  console.log(recaptcha);
  if (recaptcha != null) {
    grecaptcha.ready(function() {
      grecaptcha.execute('{{YOUR_SITE_KEY}}', {action: 'homepage'}).then(function(token) {
        recaptcha[0].value = token
      }, function (reason) {
        console.log(reason)
      });
    });
  }
</script>
<!-- /reCAPTCHA -->

homepageの部分は以下4つから選べる。
* homepage:See a cohesive view of your traffic on the admin console while filtering scrapers.
* login:With low scores, require 2-factor-authentication or email verification to prevent * credential stuffing attacks.
* social:Limit unanswered friend requests from abusive users and send risky comments to moderation.
* e-commerce:Put your real sales ahead of bots and identify risky transactions.

フォーム内にトークン送信用のフィールドを設置

some field... 
<input type="hidden" name="recaptcha_token" value=""/>
... submit button

ここまででアクセスした時点でサイトの右下にシールがつきます。
valueにもすぐにパラメータが入ってきます。

3.バックエンド(Go)

問い合わせ用の関数と結果の受け取り用のstructはこんな感じ。
"YOUR_SITE_SECRET"を取得したシークレットに置き換えてくださいね。


type RecaptchaResponse struct {
    Success     bool      `json:"success"`
    Score       float64   `json:"score"`
    Action      string    `json:"action"`
    ChallengeTS time.Time `json:"challenge_ts"`
    Hostname    string    `json:"hostname"`
    ErrorCodes  []string  `json:"error-codes"`
}

func reCaptchaVerify(token string) (r RecaptchaResponse, err error) {
    const recaptchaServerName = "https://www.google.com/recaptcha/api/siteverify"
    resp, err := http.PostForm(recaptchaServerName,
        url.Values{"secret": {"YOUR_SITE_SECRET"}, "remoteip": {"127.0.0.1"}, "response": {token}})
    if err != nil {
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return
    }
    err = json.Unmarshal(body, &r)
    if err != nil {
        return
    }
    return
}

フォームから送信されたトークンを渡します。

if r, err := reCaptchaVerify(r.FormValue(FormNameRecaptcha)); err == nil {
  if r.Success && r.Score > 0.7 {
      // success action
    } else {
      // error action
    }
  }
}

スコアはユーザーエージェントの付け替えや、広告ブロック等の状態で変動するそうです。
広告ブロックくらいは許して良いだろという事で0.7くらいで丁度いいかなと思いますが
実際に動かして調整してみると良いかもしれません。

参考

GoogleのreCAPTCHA v3をPHPで使う