Java/SpringでreCAPTCHA v3を実装する

# はじめに
ググってもJava向けの記事がなかったのと、公式サイトは若干わかりづらかったのでまとめてみました。

## 公式リンク
- [Google reCAPTCHA公式](https://www.google.com/recaptcha/intro/v3.html)
- [フロントエンド実装について](https://developers.google.com/recaptcha/docs/v3)
- [バックエンド実装について](https://developers.google.com/recaptcha/docs/verify)

## 参考リンク
- [お問い合わせフォームにreCAPTCHA v3を導入してみました！](https://knowledge.moshimore.jp/entry/recaptcha_introduction)
    - 基本的な実装が簡潔にまとまっている
- [reCAPTCHA v3 入れてみた](https://www.techscore.com/blog/2018/12/06/recaptcha-v3/)
    - エラー分岐なども考慮してまとまっている

# 登録方法
まずサービスを利用するために登録を行います。

[お問い合わせフォームにreCAPTCHA v3を導入してみました！](https://knowledge.moshimore.jp/entry/recaptcha_introduction)
⇒「reCAPTCHA v3の登録方法」を参照

# 実装方法
登録が済めば後は実装するだけです。
## クライアント側
### HTML
formタグ内に以下を追記。
JavaScriptによって取得した`token`をこのvalueに設定して、submitします。
Ajaxを使うなら、直接`token`をRequestパラメータに指定することによりこの記述は不要です。

```html:hoge.html
<input type="hidden" name="recaptchaResponse" id="recaptchaResponse">
```

script読み込み部分に以下を追記。
自サイトでreCAPTCHAを使えるようにします。
`（サイトキー）`には登録時に取得したサイトキーを代入します。

```html:hoge.html
<script src="'https://www.google.com/recaptcha/api.js?render=' + （サイトキー）"></script>
```

### JavaScript
form submit時に実行される関数を以下のように実装。
actionに設定した値（以下では`'contact_form'`）はreCAPTCHAからのResponseに格納されるので、サイト上のどこで実行されたかなどを設定しておくと分析とかに使えるかも。

```javascript:hoge.js
$form = $(/* formを取得 */);
$button = $(/* form内のsubmitボタンを取得 */);

$button.click(function() {
		// reCAPTCHA v3用
		grecaptcha.ready(function () {
			grecaptcha.execute(（サイトキー）, {action: 'contact_form'}).then(function(token) {
				$('#recaptcha-response').val(token);

				// reCAPTCHA実装前に関数内に記述していた処理はここに記述				

				$form.submit();
			});
		});
});
```

## バックエンド側
### Controller
POST先のメソッドを以下のように実装。実際にはロジック部分はService層に切り出すこと。
BOT判定（if文の部分）については次のModelセクションで解説します。

```java:HogeController.java
@RequestMapping(value = "/hoge", method = RequestMethod.POST)
public String hogePost(
		@Valid @ModelAttribute("hogeForm") HogeForm hogeForm,
		BindingResult bindingResult,
		HttpServletRequest request, SitePreference sitePreference, Model model) {

	String url = "https://www.google.com/recaptcha/api/siteverify?secret=" + （シークレットキー） + "&response=" + hogeForm.getRecaptchaResponse;
	RestTemplate restTemplate = new RestTemplate();
	RecaptchaResult result = restTemplate.getForObject(url, RecaptchaResult.class);

	log.info("reCAPTCHA result: " + result.toString());

	if (result.isSuccess()) {
		if ( 0.5 <= result.getScore()) {
			// BOTと判定されなかった場合の処理を記述
		} else {
			// BOTと判定された場合の処理を記述
		}
	} else {
		// reCAPTCHAに接続失敗した場合の処理を記述
	}
}
```

### Model
reCAPTCHA APIのResponseを格納するModelを以下のように実装。
getterとsetterを自動生成するlombokを使用しています。
Responseの内容は[公式](https://developers.google.com/recaptcha/docs/v3#site_verify_response)を参照してください。

#### 重要なこと
`success`プロパティはAPIの接続の成功・失敗を表すものであり、BOTかどうかの判定ではないことに注意する。
reCAPTCHAでは返却された`score`プロパティ（0.0~1.0）を基にBOTかどうかを自プログラムで判断する。`score`が1に近いほどBOTの可能性が低く、0に近いほどBOTの可能性が高い。
[Google公式 Interpreting the score](https://developers.google.com/recaptcha/docs/v3#interpreting_the_score)曰く、最初の閾値は0.5でいいとのこと。

```java:RecaptchaResult.java
package com.croooober.v1.cr_www.model.api;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class RecaptchaResult {
    private boolean success;
    private String challenge_ts;
    private String hostname;
    private float score;
    private String action;

    public RecaptchaResult() {
    }
}
```

また、Formオブジェクトにプロパティを追加。

```java:HogeForm.java
private String recaptchaResponse;
```

うまく実装できていれば、以下のようにlog出力で結果を確認できると思います。

```
reCAPTCHA result: RecaptchaResult(success=true, challenge_ts=2020-01-08T04:19:27Z, hostname=hoge.com, score=0.9, action=contact_form)
```

# 感想
最初はAPIに投げればBOTかどうか判定してくれるものと思っていました。
実際には`score`を基に自プログラムで判定する必要があって少しだけ混乱しました。