Help us understand the problem. What is going on with this article?

Google reCAPTCHAの実装方法【v2非表示 ver】【Laravel】

Google reCAPTCHAを実装する

Bot攻撃による大量申し込みを受けてreCAPTCHAを導入することになったのでその手順を記します。

reCAPTCHAの実装方法がよく分からない..という方は参考になるかもしれません。

v2とv3がありますが、なるべくユーザーによる申し込みを弾かないように、かつ最低限のBot対策はできるようにしたかったので、
v2の非表示バージョンで実装をしました。

v2のチェックボックスやv3の方法は今回扱っていません。

分かりやすくするためにPHPフレームワークのLaravelを例にとって説明していますが、他のフレームワークやいわゆるVanilla PHPでも同じように実装できるかと思います。

環境はLAMPを想定しています。

Google reCAPTCHA Invisibleの設定手順

管理画面での設定

まずはサイトごとのAPIキーを発行しないといけないので管理画面で設定していきます。

Google reCAPTCHAのサイトへアクセス。
https://www.google.com/recaptcha/intro/v3.html

Image201905211254.png

Admin consoleへ移動したら、新しくサイトを追加します、

Image201905211256.png

「ラベル」は好きな名前で大丈夫です。

「セキュリティ設定」はレベルが3段階あり、
2~3のレベルで設定してみたところ、ユーザーによる申し込みも画像認証で弾かれてしまうことがあったので、
「ユーザーの負担が最小」である1段階のレベルで設定をすることに決めました。

grc2.png

上記で確定すると、APIのサイトキー・シークレットキーが発行されます。

次に、Laravel上で設定作業を行っていきます。

JSタグの設置

まずAPIのJSファイルを読み込みます。
テンプレートファイルの<head>内に挿入。

resources/views/form/input.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>FooBar Form</title>
+       <script src="https://www.google.com/recaptcha/api.js" async defer></script>
    </head>
    <body>
        // contents

        <form action="entry/input/" method="post" id="form">
            // form inputs
        </form>
    </body>
</html>

フロント側のタグ・スクリプト設定

configを設定

APIキーはバージョン管理しないほうがよいので.envで設定します。
APP_RECAPTCHA_SITE_KEYは先ほど管理画面で発行したサイトのキー、
APP_RECAPTCHA_SECRET_KEYは先ほど発行したシークレットキーです。

.env
+ APP_RECAPTCHA_SITE_KEY=your_site_key
+ APP_RECAPTCHA_SECRET_KEY=your_secret_key

設定したファイルを読み込みます。
APIのエンドポイントもここに設定しておきます。

config/app.php
    /*
    |--------------------------------------------------------------------------
    | reCAPTCHA Settings
    |--------------------------------------------------------------------------
    |*/
+    'recaptcha' => [
+        'api_url' => 'https://www.google.com/recaptcha/api/siteverify',
+        'site_key' => env('APP_RECAPTCHA_SITE_KEY'),
+        'secret_key' => env('APP_RECAPTCHA_SECRET_KEY'),
+    ],

];

reCAPTCHAレンダリング対象となるdivタグ設置

reCAPTCHAのレンダリングに必要となるので設定します。
data-sitekey属性にサイトキー、data-callback属性にsubmit時のコールバック関数が入ります。

resources/views/form/input.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>FooBar Form</title>
        <script src="https://www.google.com/recaptcha/api.js" async defer></script>
    </head>
    <body>
        // contents

        <form action="./entry/input" method="post" id="form">
+                <div class="g-recaptcha"
+                        data-sitekey="{{ config('app.recaptcha.site_key') }}"
+                        data-callback="onGrecaptchaSubmit"
+                        data-size="invisible"></div>
            // form inputs
        </form>
    </body>
</html>

reCAPTCHAを呼び出すためのAPI設定

コールバック関数をグローバルで定義しておきます。

また、自分の場合はJSによるバリデーションをかけていて、
reCAPTCHAのAPIを発火させるタイミングは「submitボタンが押されたとき」ではなく「バリデーションが通ったとき」にしたいので、

reCAPTCHAにより提供されているgrecaptcha.execute()関数を使用して、任意のタイミングでreCAPTCHAを実行させます。

resources/js/form-efo.js
+ window.onGrecaptchaSubmit = function(request) {
+    $('form').submit();
+ };

$(() => {
    // 省略・・・

    if (validated()) {
      // 省略・・・
-     $('form').submit();
+     grecaptcha.execute();
    }
    // ・・・
});

ここまででフロント側の設定は完了です。

ローカル環境で見てみると右下にreCAPTCHAのアイコンが出ているかと思います。

普通に人間が入力してsubmitするとそのまま進めますが、
自動入力などを行うと画像認証画面が出てきて引っかかるようになります。

バックエンド側のreCAPTCHA認証実装

クライアント側だけですと不正に突破される可能性もあるので、バックエンド側でも認証のチェックを行います。

簡単に言いますとクライアント側でreCAPTCHAのAPIが発火した時点でトークンが発行され、
フォームのsubmit時にg-recaptcha-responseというキーでトークンがPOSTされるので、そのトークンをバックエンド側でチェックします。

ミドルウェア作成

いろいろ方法はありますが、今回はミドルウェアを利用します。
curlを使っていますがGuzzleなどのライブラリを使用してもいいかと思います。

発行したシークレットキー・クライアント側で発行されたトークン・IPアドレスをもとに、不正なsubmitでないかをチェックします。

APIについて詳しく知りたい方は公式のドキュメント参照してみてください。
https://developers.google.com/recaptcha/docs/verify

app/Http/Middleware/CheckRecaptcha.php
<?php

namespace App\Http\Middleware;

use Closure;

class CheckRecaptcha
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->input('g-recaptcha-response')){
            $secret_key = config('app.recaptcha.secret_key');
            $url = config('app.recaptcha.api_url');
            $token = $request->input('g-recaptcha-response');

            $ch = curl_init();
            curl_setopt_array($ch, [
                CURLOPT_URL => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_POST => true,
                CURLOPT_POSTFIELDS => http_build_query([
                    'secret' => $secret_key,
                    'response' => $token,
                    'remoteip' => $request->server->get('REMOTE_ADDR'),
                ]),
            ]);
            $_response = curl_exec($ch);
            curl_close($ch);

            $response = json_decode($_response);

            if (isset($response->success) && $response->success) {
                return $next($request);
            }
            else {
                return redirect('error'); // エラーページへリダイレクト
            }
        }
        return redirect('error'); // エラーページへリダイレクト
    }
}

ここからはLaravelだけの話なので、他のフレームワークでしたら飛ばしてオッケーです。

Kernelにミドルウェアを登録し、

app/Http/Kernel.php
    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        // 省略・・・
+       'recaptcha' => \App\Http\Middleware\CheckRecaptcha::class,
    ];

申し込みフォームのルーティングにミドルウェアを割り当てます。

routes/web.php
- Route::post('/entry/input', [\App\Http\Controllers\EntryInputController::class, 'input'])->name('entry.input');
+ Route::post('/entry/input', [\App\Http\Controllers\EntryInputController::class, 'input'])->name('entry.input')
+     ->middleware('recaptcha');

これでreCAPTCHAの認証設定は完了です。

補足:ログを出力する設定

バックエンドでAPI認証する際に、レスポンスの内容を出力したいということがあるかもしれません。

自分の場合は下記のようにログ出力の設定を行いました。
認証エラーが発生した場合ステータスコードと、エラー内容が分かるのでデバッグがやりやすくなるかと思います。

app/Http/Middleware/CheckRecaptcha.php
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->input('g-recaptcha-response')){
            $secret_key = config('app.recaptcha.secret_key');
            $url = config('app.recaptcha.api_url');
            $token = $request->input('g-recaptcha-response');

            $ch = curl_init();
            curl_setopt_array($ch, [
                CURLOPT_URL => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_POST => true,
                CURLOPT_POSTFIELDS => http_build_query([
                    'secret' => $secret_key,
                    'response' => $token,
                    'remoteip' => $request->server->get('REMOTE_ADDR'),
                ]),
            ]);
            $_response = curl_exec($ch);
+           $_error = curl_error($ch);
            curl_close($ch);

            $response = json_decode($_response);
+           Log::info(
+               $_response ? [
+                   'recaptcha-result' => $response->success ? $response->success : $response['error-codes']
+               ] : ['recaptcha-error' => $_error]
+           );
+          
            return is_null($response) ? false : $response->success;
        }
        return false;
    }

Google reCAPTCHAの実装はLaravelだと簡単 !

今回はv2非表示のセキュリティレベル1で設定しましたが、Selenium製ロボットも弾いてくれますし、ユーザーが申し込みできないといったことも今のところはありません。

比較的簡単な実装でBot対策できますので、是非やってみてください。


※参考ページ
https://developers.google.com/recaptcha/docs/invisible
日本語版が無かったので今回まとめてみました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした