blog

GoogleReCaptcha3を使ってSpringBootアプリで不正なリクエストをフィルタリングする

さて、ログイン、登録、SMS認証コードのアプリケーションです。CAPTCHAは、これらのシナリオのために本当に不可欠です。技術の発展に伴い、また、元のグラフィカルなCAPTCHAからCAPTCHAを作...

Aug 18, 2020 · 8 min. read
シェア

今日、ログインや登録のためのアプリケーションは、SMS CAPTCHAを使用しています。これらのシナリオでは、CAPTCHA は本当に不可欠です。技術の発展に伴い、また、元のグラフィカルなCAPTCHAからCAPTCHAを作り、今日のスライダーに、反転テキストのクリック、数学的計算、ジェスチャー、スライド、ジグソーパズル、スクレイピング...などなど、様々なトリック。このように、あらゆる種類のトリック、要するに、目的は、ロボットへのアクセスを防ぐことです。

CAPTCHAというのはユーザーにとって本当に嫌な経験で、多くのアプリは本当に難解なCAPTCHAを持っていて、人々を狂わせます。

ReCaptcha

これはGoogleCaptchaプログラムで無料で、強力で、多くのワールドクラスのアプリが使用しています。

現在では、さらに人気の高い2つのバージョンがあります!

ReCaptcha 2次のようになります。

このキャプチャを見ると、かなりイライラして心配になります。

ReCaptcha 3

見せられない... そう、写真もプッシュボタンもありません。これは最も高度なバージョンで、ユーザーに嫌がらせをするわけではありません。その代わり、クライアントからいくつかの環境データを密かに読み取り、サーバーに送信し、サーバーはそれを数値で評価します。アプリケーションはこのスコアを使って、現在のリクエストが正当かどうかを判断する必要があります。

以下では、ReCaptcha 3にアクセスする場合のデモを、登録から始めます。

登録

以下のアドレスが開けない理由は聞かないでください。

登録はwww.google.com/recaptcha/a

簡単で、必要な名前とアクセスするアプリケーションのドメイン名を記入するだけです。それだけです。

登録成功

覚えておく必要があるキーは2つあります。つはフロントエンド鍵と呼ばれ、クライアントに公開されます。もう一つはバックエンドキーと呼ばれ、リモートサーバとの通信に使用され、クライアントには公開されません。

管理コンソール

www.google.com/recaptcha/a...

コンソールでは、リクエスト数などの検証情報を見ることができます。

ドキュメント

developers.google.com/recaptcha/i...

詳しくは公式ドキュメントをご覧ください。

SpringBootアプリケーションの作成

バリデーション・インターセプターをstart.springboot.io/

作成プロセスは省略され、サードパーティの依存関係は必要なく、基本的なSpringBootの依存関係があります。

ymlでキーを設定します。

recaptcha:
 client-key: "6LdvzboZAAAAALrLVyjabnd5xfb06izncgK0JXCt"
 server-key: "6LdvzboZAAAAAE1mLwijdq9noxVUbWRBmqm-4Ava"

ymlで設定すると、プログラムは柔軟に

Webクライアントとの統合

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>ReCaptcha V3</title>
		<!-- renderアノテーションの値はフロントエンドのキーである>
		<script src="https://..//.?=Ct"></>
	</head>
	<body>
		<input name="name" placeholder="名前を入力する " id="name"/>
		<button id="button"> </button>
	</body>
	<script type="text/javascript">
		// フロントエンドのキー
		const CLIENT_KEY = "6LdvzboZAAAAALrLVyjabnd5xfb06izncgK0JXCt";
		
		// grecaptcha.ready => CAPTCHA 初期化成功後のコールバック
		grecaptcha.ready(() => {
			console.log('CAPTCHA 初期化 OK');
		});
		
		// フォーム投稿
		document.querySelector('#button').addEventListener('click', () => {
			
			// grecaptcha.execute => トークンを生成する
			/**
				最初のパラメータはフロントエンドのキーである
				2番目のパラメータはオブジェクトで、アクション属性はカスタムの "シーン名 "だ。”.アプリはN個の検証シナリオを持つことができ、バックグラウンドで異なるシナリオの検証データを見ることができる。
				3番目のパラメータはコールバック・メソッドで、トークンが正式なパラメータとなる。
			**/
			grecaptcha.execute(CLIENT_KEY, {action: 'test'}).then((token) => {	// リクエストボディを構築する
				const body = new URLSearchParams();
				body.set('token', token);									// CAPTCHAコールバックトークン
				body.set('name', document.querySelector('#name').value);	// フォームデータ// リクエストを開始する
				fetch('/test', {
					method: 'POST',
					body: body
				}).then(resp => {
					if (resp.ok){
						resp.json().then(message => {
							if (message.success){
								document.querySelector('#name').value = '';
							} else {
								alert('人間による検証は失敗する');
							}
						})
					}
				});
			});
		});
	</script>
</html>

テンプレートエンジンのレンダリングであれば、設定ファイルからフロントエンドのキーをページにレンダリングすることができます。

注目すべき点

jsライブラリ読み込み

<script src="https://..//.?er={クライアント・アイ}"></>

CAPTCHA 初期化完了コールバック。

grecaptcha.ready(() => {
	console.log('CAPTCHA 初期化 OK');
});

executeメソッドを実行してトークンを取得します。

grecaptcha.execute("{クライアント・キー}", {action: '{action}'}).then((token) => {
	// トークンを取得したら、データをサーバーに送信する。
});

サーバーサイドのバリデーション

バリデーションのステップ

1.Httpクライアントを使用して、3つのパラメータを持つリモートサーバーへのPOSTリクエストを開始します。

https://..///fy
secretバックエンドキーで御座います
responseクライアントが生成したトークンで御座います
remoteipクライアント ipいいえ

2.サーバーの応答

{
 "success": true|false, // このリクエストは、このサイトにとって有効なreCAPTCHAトークンか?
 "score": number // 1が人間、0が機械である。
 "action": string // 定義された検証シナリオ
 "challenge_ts": timestamp, // ロードされたタイムスタンプ
 "hostname": string, // reCAPTCHAを使用しているサイトのホスト名
 "error-codes": [...] // オプションのエラー・コード
}

エラーコードの説明

3.評価によって出すか出さないかを自分で判断してください。

完全なコード

import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@RestController
@RequestMapping("/test")
public class TestController {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);
	
	// 最初にIOCにRestTemplateを登録する必要がある。
	@Autowired
	private RestTemplate restTemplate;
	
	// 設定ファイルからバックエンドのキーを読み込む
	@Value("${recaptcha.server-key}")
	private String serverKey;
	
	// リクエスト・アドレス
	private static final String SITEVE_RIFY = "https://..///fy";
	
	@PostMapping
	public Object test (HttpServletRequest request,
						@RequestParam("name") String name,
						@RequestParam("token") String token) {
		
		LOGGER.info("name={}, token={}", name, token);
		
		HttpHeaders httpHeaders = new HttpHeaders();
		httpHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
		httpHeaders.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
		MultiValueMap<String, Object> requestBody = new LinkedMultiValueMap<>();
		requestBody.add("secret", this.serverKey);		// サーバー側のキー
		requestBody.add("response", token);				// クライアント側でのトークン送信
		requestBody.add("remoteip", request.getRemoteAddr()); // クライアントのIPアドレスは必須パラメータではない。
		
		ResponseEntity<String> responseEntity = restTemplate.postForEntity(SITEVE_RIFY, new HttpEntity<>(requestBody, httpHeaders), String.class);
		JsonObject jsonObject = JsonParser.parseString(responseEntity.getBody()).getAsJsonObject();
		
		LOGGER.info("recaptcha response={}", jsonObject);
		
		
		// 実行が成功したかどうか
		if (!jsonObject.get("success").getAsBoolean()){
			// 失敗した場合は、例外ステータスコードを取得する。
			JsonArray errorCodes = jsonObject.get("error-codes").getAsJsonArray();
			LOGGER.error("recaptcha error={}", errorCodes);
			return Collections.singletonMap("success", Boolean.FALSE); 
		}
		
		//  
		double score = jsonObject.get("score").getAsDouble();
		
		if (score < 0.5) {
			// もし0以下なら.5スコア、サービスはリクエストを受け入れない
			return Collections.singletonMap("success", Boolean.FALSE); 
		}
		
		return Collections.singletonMap("success", Boolean.TRUE);
	}
}

実行ログ1件

i.s.web.controller.TestController : name=qwdqw, token=03AGdBq26J8YBZT6VuzU27VyuOk-KmKxN-UB6ETQ_MKOuDyea8upMmMBsX6H3TZ5NNK_VwgvJpxEJVppdmHNERK2d4Eo_w-YpxCi0TJmTyWJLRXD7279DScPOLxuRbj0nH_pTyYJw7OCf9o06gOeBQUqF7bCI_I4rakW4LvQSXd5d2jyFBdOf-FET6vqYzOYB93LyOsKcZdMci9YxIJ-9p8x_gm9YetFvyzQBt5il7iDHEqeLAd7HfLSh6UVOeDtHDncbkIgKWitHv4DuEO8_O8Pm7Fz6Sdc_GoAJgPeYAHkZs5vMvPqwv6H7hUKhh8RI-zCm3cYKe6nYK3Fc7Mc1Xr5bRnJqrSgrJkLBva4v2y-gSffm7E8GmtFgE9Kgr1iaualNUVzmYAiTx
i.s.web.controller.TestController : recaptcha response={"success":true,"challenge_ts":"TZ","hostname":"localhost","score":0.9,"action":"test"}

プライバシーポリシーアイコン

このバージョンのCAPTCHAを使用すると、画面の右下に小さなアイコンが表示されます。結局のところ、他の人のを使った後、それを維持することをお勧めします。

非表示にするには、cssを追加してください。

.grecaptcha-badge { 
	display: none; 
} 

最後に

汎用性を考えると、とてもシンプルです。バリデーション・インターセプターを抽象化し、アノテーションを定義します。アノテーションには、そのインターフェイスで最低限許されるスコアを記述します。アノテーションの値はインターセプターで取得され、判定とバリデーションが行われます。このほうがはるかに汎用的で柔軟です。

Read next

モニタリングの壁を打ち破り、綿工場の3Dビジュアライゼーションが生産と加工をよりスマートにする

現在、綿花加工業界では、綿花加工工場の設備が突然故障した場合、制御プログラムを交換する必要があるという、伝統的なリアクティブ・メンテナンス・モデルから抜け出せずにいます。この場合、顧客はまず設備メーカーに技術者を派遣してメンテナンスを実施するよう要請する必要があり、メーカーは状況に応じて現場に人を派遣して対応します。綿花加工設備は中国全土に分布し、世界にも輸出されているため、お客様の対応から問題...

Aug 17, 2020 · 4 min read