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


![スクリーンショット 2019-04-12 16.45.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15518/2eea33f0-1ecd-d896-188f-dac9f432c3f5.png "スクリーンショット 2019-04-12 16.45.23.png")


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

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

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

こんな感じです。詳しくは[公式ドキュメント](https://developers.google.com/recaptcha/docs/v3)に。
PHP版は書いている方がいたので（[GoogleのreCAPTCHA v3をPHPで使う](https://qiita.com/bunty/items/ca235670755b74767114)）
今回はバックエンド部分をGoでさくっとやってみます。

[githubに全ソース置きました](https://github.com/supertaihei02/go-login-recaptchav3)
キーさえ取ればdocker-composeで即動かせます。

## 1.KeyとSecretを取得

Googleアカウントが必要です。
[https://www.google.com/recaptcha/](https://www.google.com/recaptcha/)にアクセスし、Admin Consoleからサイトキー、サイトシークレットを取得します。
![スクリーンショット 2019-04-12 16.51.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15518/3641c91b-4e4e-f730-c5e5-82e6c3095c47.png "スクリーンショット 2019-04-12 16.51.20.png")

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


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

`{{YOUR_SITE_KEY}}`は取得したキーを置き換えてください。[githubのソース](https://github.com/supertaihei02/go-login-recaptchav3)ではgoのtemplateを使ってます。

```javascript
<!-- 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.


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

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

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

## 3.バックエンド（Go）

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

```go

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
}

```

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

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

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

![スクリーンショット 2019-04-12 16.34.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15518/c7c7b954-c98f-1271-9ff1-63f7c06bd5dc.png "スクリーンショット 2019-04-12 16.34.40.png")


## 参考
[GoogleのreCAPTCHA v3をPHPで使う](https://qiita.com/bunty/items/ca235670755b74767114)