在 Laravel 中使用 Google 的 reCAPTCHA

sharkHead 程式技術 7個月前 • 0

CAPTCHA,全稱為

全自動區分電腦和人類的公開圖靈測試
(Completely Automated Public Turing test to tell Computers and Humans Apart)

我們比較熟悉的叫法應該是驗證碼,是一種用來判斷使用者是否為真人還是機器人(Bot)的一種方式
CAPTCHA 後來改版成 reCAPTCHA,並被 Google 收購

在網站中使用 reCAPTCHA,目的是為了避免網站被惡意的機器人攻擊
像是註冊多組帳號,以此濫用服務或是製造大量留言

Google 的 reCAPTCHA 分為兩個版本,分別是 v2 與 v3
v2 對常上網的人來說可能很眼熟,因為各大網站的登入可能都會有

%E6%88%AA%E5%9C%96%202020-09-11%20%E4%B8%8B%E5%8D%8810_31_55.png
登入頁面下方很常出現的 「我不是機器人」

v2 這種驗證碼雖然可以有效的防止機器人,但對用戶來說,其實體驗上並不算上友好
如果你被認定為機器人,你就要開始認識外國的紅綠燈、汽車或是斑馬線

captcha_traffic_light.jpg
請點選有紅綠燈的區塊

為了增進使用者體驗,Google 後來推出了 v3
v3 會利用 AI 去主動分析使用者的行為,並給一個分數
當分數太低時,就會被認為是機器人,並開始認識紅綠燈的測驗
如果網站有 v3,你可能會在網站右下角發現這個

recaptcha_v3.jpg
有這個就代表網站有使用 v3

這個分數可以由自己去設定,但是如果分數設定不對,使用者可能在瀏覽網站時,可能時不時就要開始玩認識紅綠燈的測驗,導致使用者體驗更差
這也是為什麼 v3 無法完全取代 v2 的原因

 

在 Laravel 中使用 reCAPTCHA


前面稍微介紹 Google reCAPTCHA 
接下來進本次文章重點,在 Laravel 中會該如何使用 Google reCAPTCHA v2?

首先我們要先上 Google reCAPTCHA 的網站申請 API key
申請完之後會取得 site key 與 secret key
將這兩個 key 記錄在 Laravel 的 .env 中,之後會在 config file 中取用

%E6%88%AA%E5%9C%96%202020-09-12%20%E4%B8%8B%E5%8D%881_32_21.png
敏感資料建議都寫在 .env 中

然後我們在 config/services.php 中設定

<?php

return [
    'recaptcha_site_key' => env('RECAPTCHA_SITE_KEY'),
    'recaptcha_secret_key' => env('RECAPTCHA_SECRET_KEY'),
];

假設我們要在網站的登入中使用 reCAPTCHA,根據官方文件
我們要在 head tag 中引入 API 的 script,並在登入的 form 中插入 reCAPTCHA

<html>
    <head>
        <title>reCAPTCHA demo: Simple page</title>
        <!-- 引入 reCAPTCHA 的 JS 檔案 -->
        <script src="https://www.google.com/recaptcha/api.js" async defer></script>
    </head>
    <body>
        <form action="?" method="POST">
        	<!-- 表單中加入驗證的區域 -->
            <div class="g-recaptcha" data-sitekey="your_site_key"></div>
            <br/>
            <input type="submit" value="Submit">
        </form>
    </body>
</html>

後台的部分,request 接收到的 name 是 g-recaptcha-response
這裡我們會使用 laravel 提供的 rule 功能,自訂一個 Recaptcha 的 rule 來驗證 reCAPTCHA 的值是否正確
下一步會說明如何建立這個 rule

<?php
// 自己定義一個 Recaptcha 的 Rule 來檢查驗證碼
use App\Rules\Recaptcha;

protected function validateLogin(Request $request)
{
    $request->validate([
        $this->username() => 'required|string',
        'password' => 'required|string',
        // 在 validate 使用自定義的 Rule
        'g-recaptcha-response' => ['required', new Recaptcha],
    ], [
        'g-recaptcha-response.required' => '請完成驗證',
    ]);
}

接下來我們要建立一個 Recaptcha 的 rule,首先輸入指令

php artisan make:rule Recaptcha

這時候就會在 app 底下新增一個 Rule/Recaptcha.php
在 Recaptcha.php 中,我們會使用 Laravel 提供的 HTTP 客戶端工具(使用 Guzzle 套件)
發送一個 POST 請求至 google reCAPTCHA 的 API 進行驗證

注意這裡請求的數據類型必須使用 application/x-www-form-urlencoded
因此在創建請求時,需要調用 asForm() 方法

<?php

public function passes($attribute, $value)
{
	$verifyUrl = 'https://www.google.com/recaptcha/api/siteverify';

	$response = Http::asForm()->post($verifyUrl, [
		'secret' => config('services.recaptcha_secret_key'),
		'response' => $value,
	]);

	// 取得 response 的 success 值
	return $response->json()['success'];
}

返回的 $response 為 JSON 格式,這裡使用 json() 方法轉成 Array 格式

[
  "success" => true
  "challenge_ts" => "2020-09-21T12:31:20Z"
  "hostname" => "website.com"
]

陣列中的 success 為布林值,如果為 true,代表驗證成功

如此一來這樣就可以在登入頁面中使用 reCAPTCHA 來防止惡意機器人了

這邊可能會有人有疑惑,為什麼不直接在 app\Rules\Recaptcha.php 中使用 env() 去拿 API Key 就好
反而先在 config 檔案中設定,在使用 config() 去拿 API key
這是因為,如果專案上到正式環境,我們通常會使用 php artisan config:cache 這個指令來幫 config 設定進行快取
但只要一進行這個動作,在非 config 的檔案中(controller 或是 blade),使用 env() 都只會拿到空值
Laravel 的文件中也有提到這一點


sharkHead

PHP 與 Python 菜雞工程師
最近在努力學習 TypeScript,希望可以突破慧根的限制