reCAPTCHAを実装した話
書いたら忘れそうな気がするけどとりあえず
■そもそもreCAPTCHAとは
私はロボットではありません的なアレ。
バージョン的にはv1~v3まであるらしいが上位互換的な話ではないらしい。
v1:
文字によって認証させる。ぐにゃぐにゃした文字のやつ。
見た記事にはbotが優秀になってそれに対抗するために複雑化して遂に人間が目で見て識別できなくなったからサポートが終了したみたいに書いてある。本当ならウケる。
v2:
チェックボックスに反応して画像選択にすることで人間を識別する。信号機とか選ぶよね。
派生系としてInvisibleとかいうのがあってそれはもうチェックボックスすらない。
v3:
どうやらアクセスに対してスコアが付けられるみたい。そのスコアを確認して好きな処理を走らせられるみたい。画像選択じゃなくて2要素認証とか
v2とv3の違いに関してはリクエストを通すかどうかの問題みたい。v2は怪しいアクセスに対して画像選択とかで制限をかけてるけどv3の場合はスコア値によって処理を自作できるよう。
■実装
環境:PHP8.0.2
FW:laravel9
DB:mysql5.7
ライブラリがいくつかあるみたいだけど「arcanedev/no-captcha」を採用。なんか実装サイトわかりやすかった。色々省略します。
・インストール
composer require arcanedev/no-captcha:"^13.0"
laravel9なのでライブラリのバージョンは13.X
・env設定
Google reCAPTCHAの設定が必要。そんなむずくなかった。
NOCAPTCHA_SITEKEY=xxxxxxxxxxxxxxxxxxxxx
NOCAPTCHA_SECRET=xxxxxxxxxxxxxxxxxxxxx
・config用意
php artisan vendor:publish --provider="Arcanedev\NoCaptcha\NoCaptchaServiceProvider"
今回はv3を実装するので変更はなし
・view側
<form id="login-form">
<button id="login-btn" type="submit">
ログイン
</button>
{!! no_captcha()->input() !!}
</form>
{!! no_captcha()->script() !!}
{!! no_captcha()->getApiScript() !!}
<script>
var btn = document.getElementById('login-btn');
btn.addEventListener('click', function (e) {
e.preventDefault();
grecaptcha.ready(function () {
const siteKey = @json(config('no-captcha.sitekey'));
grecaptcha.execute(siteKey, {action: 'login'}).then(function (token) {
document.getElementById('g-recaptcha-response').value = token;
document.getElementById('login-form').submit();
})
})
}, false)
</script>
基本的にメソッド呼び出して色々置いてく。
js内のactionに関してはなんでもいいらしい。
・フォームリクエスト(バリデーション)
public function rules()
{
return [
'emailAddress' => [
'required',
'email',
'max:50',
],
'password' => [
'required',
'max:50',
],
'g-recaptcha-response' => [
'required'
],
];
}
public function withValidator(Validator $validator): void
{
$validator->after(function ($validator) {
$url = 'https://www.google.com/recaptcha/api/siteverify';
$data = [
'secret' => config('no-captcha.secret'),
'response' => $this['g-recaptcha-response'],
];
$context = [
'http' => [
'method' => 'POST',
'header' => implode("\r\n", array('Content-Type: application/x-www-form-urlencoded',)),
'content' => http_build_query($data)
],
];
$apiResponse = file_get_contents($url, false, stream_context_create($context));
$result = json_decode($apiResponse);
$isSuccess = $result->success; // 成否
$score = $result->score; // スコア
if (!$isSuccess || $score <= config('const.RECAOTCHA_FAILD_NUM')) {
$validator->errors()->add('g-recaptcha-response', __('login.custom_err_msg.recaptcha_error'));
}
});
}
ルール的にはg-recaptcha-responseが必須なだけ
その後にAPI通信して成否とかスコアとかを取る。
例の場合だとconfig('const.RECAOTCHA_FAILD_NUM')の部分で0.5を設定していて0.5以下は足切りでバリデーションエラーとなるようにしている。(1に近いほど人間扱い。試した時は0.9)
以上。
見たことあるやつが実装できるのは楽しい。