手把手教你如何替自己的專案引入 Google reCAPTCHA v3

Google reCAPTCHA

前言

這一篇我將會手把手來介紹該如何替自己的專案加入 reCAPTCHA,以及說明為什麼要使用 reCAPTCHA 並且它有什麼優缺點。

什麼是 reCAPTCHA 服務

reCAPTCHA 其實是由 Google 所提供的驗證服務。

什麼?!你說你不知道什麼是 reCAPTCHA?沒事,讓我們來看一張動畫圖片你就一定會知道了:

reCAPTCHA

我相信你在使用網路的時候一定有遇過這個畫面,也就是我們在講的「我不是機器人」服務。

reCAPTCHA 的主要目的是防止機器人進行一些惡意行為,例如:惡意註冊、惡意登入、惡意留言等等。

那麼接下來我將會介紹如何引入 reCAPTCHA 到你的專案中,而這一篇我將會介紹使用較新的 reCAPTCHA v3 服務。

註冊 reCAPTCHA 服務

首先請你打開 Google reCAPTCHA 官方網站 並且點選上方的「Get Started With Enterprise」按鈕

Get Started With Enterprise

接著你會看到歡迎頁面,只需要修改專案名稱以及打勾下方服務項目就可以了(如果你有使用過 Cloud Platform 的話,畫面可能會有一點不同,但基本上大同小異)

歡迎頁面

設定過程中,需要稍微等一下

等待中

設定完成

等待完畢後,你就會被跳轉到 Google Cloud 的頁面,到這邊為止你已經建立並註冊好 reCAPTCHA 服務了。

設定 reCAPTCHA

基本上,你會被導向到這個頁面:

建立金鑰

一開始你需要輸入兩個欄位,分別是:

  • 顯示名稱:用於辨別該金鑰的用途
  • 選擇平台類型:選擇你的專案是在哪個平台上運行的,分別有網站、Android、iOS(這邊我們選擇網站)

這邊你可以參考我的設定,我們這邊會以網頁為例:

建立金鑰

基本上,當你選擇網站後,下方就會跳出相對應的選項,通常來講大多人會卡在「網域」這個欄位,因為我們起始開發的時候大多都是在本地上,那麼根本沒有所謂的網域,所以這邊你可以填寫 localhost 或是 127.0.0.1 這兩個都可以,主要還是看你實際測試的時候是用哪一個網域。

舉例來講,你如果打開網頁時,是用 http://localhost:3000 這個網址的話,那麼你就可以填寫 localhost 這個網域,反之如果是 http://127.0.0.1:3000 的話,那麼你就可以填寫 127.0.0.1,而這邊是不需要加入 http:// 以及 3000 這個 port 的,而這邊我將會填寫兩者,確保我不論是用哪一個網址都可以使用 reCAPTCHA 服務

網域設定

接著當你點選「建立金鑰」後,你會看到這個畫面:

新增到你的網站

接下來請你將位於「使用者進行互動時」區塊的程式碼先複製下來,稍後我們會需要使用到。

建立專案

這邊我將會使用 Express 快速建立一個示範專案,後面我也會提供完整範例檔案給你參考。

首先,請你打開終端機,並陸續輸入以下指令

1
2
mkdir recaptcha-example # 建立一個資料夾
cd recaptcha-example # 進入資料夾

接著我們會使用 npx 來建立一個 Express 專案

1
npx express-generator --view=ejs # 建立 Express 專案並使用 EJS 作為模板引擎

Note
npx 指令僅限於 Node.js 8.2.0 以上版本才有,如果你的 Node.js 版本低於這個版本,請先升級你的 Node.js 版本

建立專案

接著還原相關套件

1
npm install

最後請務必試著輸入 npm start 並且打開瀏覽器,輸入 http://localhost:3000 來確認你的專案是否正常運行。

Express 專案

將 reCAPTCHA 新增到你的網站

接著打開位於 views/index.ejs 的檔案,將剛剛 Google 所提供的 reCAPTCHA 中「使用者進行互動時」區塊的程式碼

使用者進行互動時

貼到你的 <head> 標籤中,整體大概會長的像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
<script src="https://www.google.com/recaptcha/enterprise.js?render=6Lcu8qIqAAAAANYnGO5j1XcVdt3d0DTc7ZkGT1zx"></script>
</head>
<body>
<h1> reCAPTCHA Test </h1>
<form>
<button onclick="onClick(event)">Submit</button>
</form>

<script>
function onClick(e) {
e.preventDefault();
grecaptcha.enterprise.ready(async () => {
const token = await grecaptcha.enterprise.execute('6Lcu8qIqAAAAANYnGO5j1XcVdt3d0DTc7ZkGT1zx', {action: 'LOGIN'});
});
}
</script>
</body>
</html>

貼入之後,接著點一下 Google reCAPTCHA 頁面上的「前往下一步」按鈕,這邊請你選擇「Node.js」這個選項,底下你可以看到範例程式碼:

Node.js

官方提供的範例程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const {RecaptchaEnterpriseServiceClient} = require('@google-cloud/recaptcha-enterprise');

/**
* 建立評估作業,分析 UI 動作的風險。
*
* projectID: 您的 Google Cloud 專案 ID。
* recaptchaSiteKey: 與網站/應用程式相關聯的 reCAPTCHA 金鑰
* token: 系統產生的權杖 (從用戶端取得)。
* recaptchaAction: 權杖對應的動作名稱。
*/
async function createAssessment({
// 操作說明:替換掉權杖和 reCAPTCHA 操作變數,然後執行範例。
projectID = "",
recaptchaKey = "",
token = "action-token",
recaptchaAction = "action-name",
}) {
// 建立 reCAPTCHA 用戶端。
// 任務:先快取產生用戶端的程式碼 (建議做法) 或呼叫 client.close(),再結束方法。
const client = new RecaptchaEnterpriseServiceClient();
const projectPath = client.projectPath(projectID);

// 建立評估要求。
const request = ({
assessment: {
event: {
token: token,
siteKey: recaptchaKey,
},
},
parent: projectPath,
});

const [ response ] = await client.createAssessment(request);

// 確認權杖是否有效。
if (!response.tokenProperties.valid) {
console.log(`The CreateAssessment call failed because the token was: ${response.tokenProperties.invalidReason}`);
return null;
}

// 確認是否已執行預期的動作。
// The `action` property is set by user client in the grecaptcha.enterprise.execute() method.
if (response.tokenProperties.action === recaptchaAction) {
// 取得風險分數和原因。
// 如要進一步瞭解如何解讀評估結果,請參閱:
// https://cloud.google.com/recaptcha-enterprise/docs/interpret-assessment
console.log(`The reCAPTCHA score is: ${response.riskAnalysis.score}`);
response.riskAnalysis.reasons.forEach((reason) => {
console.log(reason);
});

return response.riskAnalysis.score;
} else {
console.log("The action attribute in your reCAPTCHA tag does not match the action you are expecting to score");
return null;
}
}

透過官方所提供的範例程式碼,裡面有一行 require('@google-cloud/recaptcha-enterprise'),所以我們要替專案安裝 @google-cloud/recaptcha-enterprise 這個套件,請你在終端機中輸入以下指令:

1
npm install @google-cloud/recaptcha-enterprise

接著在 router/index.js 中加入以下我稍微改寫的程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
router.post('/recaptcha', async (req, res) => {
const recaptchaToken = req.body.recaptchaToken;
const recaptchaAction = req.body.recaptchaAction;

const projectId = process.env.PROJECT_ID;
const recaptchaKey = process.env.RECAPTCHA_KEY;

const client = new RecaptchaEnterpriseServiceClient();
const projectPath = client.projectPath(projectId);

const request = ({
assessment: {
event: {
token: recaptchaToken,
siteKey: recaptchaKey,
},
},
parent: projectPath,
});

const [ response ] = await client.createAssessment(request);

if (!response.tokenProperties.valid) {
console.log(`The CreateAssessment call failed because the token was: ${response.tokenProperties.invalidReason}`);
return null;
}

if (response.tokenProperties.action === recaptchaAction) {
console.log(`The reCAPTCHA score is: ${response.riskAnalysis.score}`);
response.riskAnalysis.reasons.forEach((reason) => {
console.log(reason);
});

return response.riskAnalysis.score;
} else {
console.log("The action attribute in your reCAPTCHA tag does not match the action you are expecting to score");
return null;
}
});

其實整體我只是稍微改寫一點點而已,分別是:

  • token:改成從 req.body 中取得,並改成 recaptchaToken
  • recaptchaAction:改成從 req.body 中取得
  • projectId:改成從環境變數中取得
  • recaptchaKey:改成從環境變數中取得

projectIdrecaptchaKey 這兩個值在 Google 所提供的範例程式碼中就有提供,所以你可以直接複製過來使用,唯獨 recaptchaTokenrecaptchaAction 這兩個值是從前端傳過來的,所以前端要改成以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function onClick(e) {
e.preventDefault();
grecaptcha.enterprise.ready(async () => {
const action = 'LOGIN';
const token = await grecaptcha.enterprise.execute('6Lcu8qIqAAAAANYnGO5j1XcVdt3d0DTc7ZkGT1zx', {action});

axios.post('/recaptcha', {
recaptchaToken: token,
action
})
.then(res => console.log(res))
.catch(err => console.error(err));
});
}

也就是說,我們在前端送出表單時,會先將當前在瀏覽器上操作行為分析送給 Google,Google 會回傳一個 token,這個 token 就是我們要送給後端的,而 action 則是我們在前端所設定的動作名稱,這兩個值會被送到後端,後端會根據這兩個值來判斷是否為真實使用者並給出分數。

但這時候如果你參考 Google 範本來改的話,估計是會出現以下錯誤:

Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.

因為我們這邊並沒有提供憑證,因為在呼叫 new RecaptchaEnterpriseServiceClient() 時,它會去找預設的憑證,但我們這邊並沒有提供,所以會出現這個錯誤。

建立憑證

首先我們要到 Google Cloud Platform 去建立一個服務帳戶,這個服務帳戶會提供一個 JSON 檔案,這個 JSON 檔案就是我們所謂的憑證。

首先請你打開 Google Cloud Platform 找到你針對這個專案所建立的專案,然後點選左邊的「IAM 與管理」,接著點選「服務帳戶」

服務帳戶

接著點選「建立服務帳戶」,接著你會被要求填寫一些資訊,這邊你可以參考我的填寫:

服務帳戶詳細資料

這邊要注意注意第二個「將專案存取權授予這個服務帳戶 (選用)」,這邊會建議你先選擇設定為「編輯者」

服務權限

第三個就可以不理會,直接按下「完成」按鈕,接著你的服務帳戶就會被建立,請你點一下你剛剛建立的服務帳戶

服務帳戶

接著點選「金鑰」,然後找到「新增鍵」按鈕,並接著點「建立新的金鑰」

建立新的金鑰

這時候會跳出一個視窗,這邊我們選擇 JSON 這個選項,然後點選「建立」按鈕

建立金鑰

此時你就可以得到一個 JSON 檔案,這個檔案就是我們所謂的憑證。

修正程式碼

你可以試著打開剛剛取得的 JSON 檔案,裡面應該會有類似以下的內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"type": "...",
"project_id": "...",
"private_key_id": "...",
"private_key": "...",
"client_email": "...",
"client_id": "...",
"auth_uri": "...",
"token_uri": "...",
"auth_provider_x509_cert_url": "...",
"client_x509_cert_url": "...",
"universe_domain": "...",
}

而我們這邊只需要 client_emailprivate_key 這兩個值,這兩個值會被用來建立憑證,所以我們要回頭修正我們的程式碼。

核心要改的地方是 const client = new RecaptchaEnterpriseServiceClient(); 要將它改成以下

1
2
3
4
5
6
const client = new RecaptchaEnterpriseServiceClient({
credentials: {
client_email: process.env.CLIENT_EMAIL,
private_key: process.env.PRIVATE_KEY
}
});

這時候你再去前端點擊「Submit」按鈕打到後端,你就可以看到後端正常運作,且會出現一些分數,這個分數就是 Google 所回傳的風險分數

風險分數

這個風險分數範圍介於 0~1 之間,0 代表是機器人的機會最高,1 代表是真實使用者的機會最高,當若被 Google 判斷為機器人時,你就可以跳出 reCAPTCHA 驗證,這樣就可以避免機器人進行惡意行為。

分數用途

這邊要稍微詳細說明一下 Google 回傳的分數

風險分數

這個分數在 Google 中稱為「風險分數」,也就是基於使用者的滑鼠移動行為、點擊模式、瀏覽器環境等等,評估出來的一個分數,而這分數範圍是 0.0~1.0,主要用於判斷是否為機器人,當分數越接近 1.0 時,代表是真實使用者的機會越高,反之分數越接近 0.0 時,代表是機器人的機會越高。

那麼我們拿到這個分數之後可以幹嘛呢?你可以用於建立一些分數門檻,例如拉出 0.50.7 或是 0.9 這些門檻,接著當分數超過這個門檻時,你就可以跳出特定的驗證來盡可能避免機器人進行惡意行為。

「疑?Google 的我不是機器人呢?不是都會出現這個驗證嗎?」

reCAPTCHA

這個是因為 Google 從 reCAPTCHA v2 => v3 之後,將會干擾使用者體驗的圖形驗證改成了透過分數來判斷是否為機器人,這樣就可以避免使用者需要進行一些繁瑣的驗證,而這個分數就是用來判斷是否為機器人的一個重要指標。

舉例來講:

  • 0.7(含以下):需要跳出手機驗證、Email 驗證等等
  • 0.7(含以上):可以直接讓他進入網站,或者填寫一些比較簡單的驗證

透過這種方式,我們可以大幅度的提升使用者體驗。

可以混用 reCAPTCHA v2 嗎?

當然可以,你可以透過你設置的門檻來決定目前是否要跳出 reCAPTCHA v2 驗證,這樣就可以讓你的網站更加安全。

但這邊就不是我這一篇文章要介紹的內容,如果你有興趣的話,我可以在下一篇文章中來介紹如何混用 reCAPTCHA v2 與 v3。

最後也提供一下 GitHub 範例程式碼:

GitHub 範例程式碼

Liker 讚賞

這篇文章如果對你有幫助,你可以花 30 秒登入 LikeCoin 並點擊下方拍手按鈕(最多五下)免費支持與牡蠣鼓勵我。
或者你可以也可以請我「喝一杯咖啡(Donate)」。

Buy Me A Coffee Buy Me A Coffee

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ