網路上總會有人為了一己私利,透過機器人(robot)來快速填寫並送出網頁上的表單,導致該網站的帳號密碼被破解,或者販賣的商品立刻被有心人士一掃而空(倒買倒賣),又或是資料庫的資料在短時間內被全部爬出來。為了防範機器人來我們的網站上搗亂,最好將Google提供的reCAPTCHA真人驗證機制加入網站中。



CAPTCHA給人的感覺就是要讓使用者去完成艱難的圖形任務,或是回答一些古怪的問題,來證明使用者自己是真人。然而Google第三代的reCAPTCHA已經完全不需再讓使用者傷腦、傷眼睛了!第三代的reCAPTCHA甚至不需要使用者用滑鼠進行任何「勾選」或是「滑動」的動作,就可以判斷使用者是否為真人。

rocket-recaptcha-v3

reCAPTCHA v3會利用範圍0.0~1.0的分數值來表示使用者可能比較像機器人還是可能比較像真人,數值愈高愈像真人。Google官方雖然建議將0.5以上的值當作真人,但實測時真人似乎都是0.9以上,所以為了擋掉更多像人的機器人,我們其實可以選用比0.5更高的數值作為門檻。

除了單純以分數值來區分機器人與真人之外,我們也可以實作額外的商業邏輯來更進一步地驗證使用者的身份。例如,使用者雖然用正確的帳號和密碼成功登入帳號,但是他的reCAPTCHA v3分數值只有0.4或是0.5時,我們可以要求他去信箱收驗證信才能繼續登入。

此外,reCAPTCHA v3還提供了action欄位,可以方便我們分析訪客們的行為。

reCAPTCHA v3 for Rocket Framework

「reCAPTCHA v3 for Rocket Framework」是筆者開發的套件,可以幫助開發者將reCAPTCHA v3加進Rocket應用程式中。

Crates.io

Cargo.toml

rocket-recaptcha-v3 = "*"

使用方法

註冊網站並取得reCAPTCHA v3的兩個金鑰

首先來到這個頁面來註冊要使用reCAPTCHA v3的網站。

rocket-recaptcha-v3

然後會得到兩個金鑰,請記錄下來。

rocket-recaptcha-v3

將reCAPTCHA v3的兩個金鑰加進Rocket的設定檔中

接著將剛才得到的reCAPTCHA v3的兩個金鑰,加進Rocket的設定檔Rocket.toml中的recaptcha欄位下的v3欄位。這個v3欄位下有一個必填欄位secret_key,即用來「建立網站和 reCAPTCHA 之間的通訊」的金鑰。還有一個選填欄位html_key,即「向使用者顯示的 HTML 程式碼中」使用的金鑰。

舉例來說:

[default.recaptcha.v3]
html_key = "6Lf6dLIUAAAAAAxghN7nH6m_yuLfHwdD3N7FpanR"
secret_key = "6Lf6dLIUAAAAAHdJ4e0nsv-8OpFH-7Oad1XQ95rq"

這個html_key欄位主要是給開發者自己用的,可以利用HTML模板引擎將html_key欄位的值填入HTML中。

將reCAPTCHA v3加入HTML中

預設的reCAPTCHA v3徽章(badge)會被浮動放置在網頁的右下角,挺難看的。所以此處簡單介紹用內聯(inline)的方式來加入reCAPTCHA v3的徽章。

首先在適當的位置上加入:

<script src="https://www.recaptcha.net/recaptcha/api.js?render=explicit&&onload=onReCaptchaLoadCallback" defer></script>
<script>
    function onReCaptchaLoadCallback() {
        var clientId = grecaptcha.render('inline-badge', {
            'sitekey': 'recaptcha_key',
            'badge': 'inline',
            'size': 'invisible'
        });

        grecaptcha.execute(clientId, {action: 'action_name'}).then(function (token) {
            // save the token
        });
    }
</script>

注意這邊引用JavaScript程式時使用的網域為www.recaptcha.net而不是www.google.com,這是為了讓中國地區也可以使用reCAPTCHA v3。而在其後的JavaScript程式碼中,recaptcha_key的地方要填入html_key欄位的值。action_name的值則設訂一個自己看得懂的名稱就好了,方便用來分析數據。inline-badge則是要用來顯示reCAPTCHA v3徽章的元素ID。

當reCAPTCHA v3執行成功後,會回傳一個記號(token),要把這個token的值記下來,之後還要跟著表單內容一同傳給我們的Web應用程式來驗證。

將reCAPTCHA v3加入Rocket中

rocket_recaptcha_v3這個crate中的ReCaptcha結構體,提供了fairing關聯函數,可以回傳一個整流片。這個整流片會在Rocket的Attach階段,讀取Rocket.toml設定檔中剛才介紹的secret_keyhtml_key欄位,來建立ReCaptcha結構體的實體,並註冊給Rocket作為應用程式狀態。

use rocket_recaptcha_v3::ReCaptcha;

...

rocket::build().attach(ReCaptcha::fairing())
驗證reCAPTCHA v3的記號

rocket_recaptcha_v3這個crate中的ReCaptchaToken結構體,可以作為參數守衛、查詢守衛或是實作了data::FromData特性的結構體的欄位。我們可以呼叫ReCaptcha結構實體的verify方法,透過參數傳入ReCaptchaToken結構實體的參考,和一個選用的ClientRealAddr結構實體參考,來驗證使用者的真人程度。

例如:

#[macro_use]
extern crate rocket;

use rocket::{form::Form, response::Redirect, State};
use rocket_recaptcha_v3::{ReCaptcha, ReCaptchaToken};

#[derive(Debug, FromForm)]
struct LoginModel {
    username:        String,
    password:        String,
    recaptcha_token: ReCaptchaToken,
}

#[post("/login", data = "<model>")]
async fn login_post(
    recaptcha: &State<ReCaptcha>,
    model: Form<LoginModel>,
) -> Result<Redirect, Redirect> {
    match recaptcha.verify(&model.recaptcha_token, None).await {
        Ok(verification) => {
            ...
        },
        Err(err) => {
            ...
        },
    }
}
驗證真人

若reCAPTCHA v3的記號驗證成功,ReCaptcha結構實體的verify方法會回傳一個ReCaptchaVerification結構實體,其score欄位即為分數值。用if結構來判斷分數值的高低來決定後續要做的動作。

例如:

#[post("/login", data = "<model>")]
async fn login_post(
    recaptcha: &State<ReCaptcha>,
    model: Form<LoginModel>,
) -> Result<Redirect, Redirect> {
    match recaptcha.verify(&model.recaptcha_token, None).await {
        Ok(verification) => {
            if verification.score > 0.7 {
                ...
            } else {
                ...
            }
        },
        Err(err) => {
            todo!();
        },
    }
}

完整的範例可以查看「reCAPTCHA v3 for Rocket Framework」專案中的examples目錄。