本篇要解決的問題
前端做的事情都是明碼,而在瀏覽器愈來愈強大的情況下,工程師們很容易可以看出來這個功能背後呼了什麼 API、帶了什麼參數。而如果被看透了,有些活動就很容易被用程式碼大量執行,比方說:投票。
一般來說,投票活動就是為了增加會員數,或是拿參加者的名單,但,有些時候,PM 死都不肯加入會員功能進去,想讓全世界的人看到頁面想投就投,
然後還要求每個人只能投幾票。
對,不用會員,然後要限制投票數。
用了會員,就可以由後端紀錄會員行為,知道他已經投了幾票。
不用會員,前端就只有 cookies 了,偏偏清 cookies 又很方便,基本上使用者自己清 cookies,前端就沒有了判斷依據,使用者想投幾票就投幾票。
「需要感謝的人太多了,就感謝天吧!」– 陳之藩 先生寫的。
「需要解決的問題太多了,就問 Google 吧!」– August 說的。
這幾天才突然想起,平常要下載一些資料時,都會遇到一個「我不是機器人」的驗證:

叮~ 腦子裡響了一下,即便無法全面防止灌票,但至少可以有效阻擋用程式來灌票的行為。
Google 了一下 reCAPTCHA,發現有 v2、v3 二版,但 v3 版的似乎很容易誤判成機器人。
而 v2 的「隱形 reCAPTCHA 標記」,感覺與其用隱形,不如直接用 v3。
因此本篇主要先來研究 v2 的「我不是機器人」核取方塊。
本篇最後會完成的 Demo 在這:
https://letswritetw.github.io/letswrite-recaptcha-v2/
最後一段會附上 Demo 頁原始碼的網址。
2020.11.18 補充:
今日完成了關於 v3 的筆記文,連結在這:
https://letswrite.tw/recaptcha-v3/
註冊 reCAPTCHA
登入了 Google 帳號後,進到 reCAPTCHA 的頁面:Google reCAPTCHA
點選右上角的「Admin Console」:

會出現一張「註冊新網站」的表單:

「標籤」就是取一個以後我們認得出是哪個站在用的名字。
「reCAPTCHA 類型」選擇「reCAPTCHA v2」,接著會出現一個小選單,選擇「我不是機器人」核取方塊:

「網域」是白名單的意思,要在這邊填寫的網域下才可以使用 reCAPTCHA 功能。
Google reCAPTCHA 並不是全免費的產品,不過不用緊張,因為免費的額度是 1 個月內 1 百萬次 XD~
但還是要限白名單較安全,因為金鑰會是明碼,全世界都看得到。
「傳送通知給擁有者」就是指當有可疑活動時,會主動收到通知信:

以上都填寫完,接受服務條款也勾選了,就可以按下「提交」。
按下完提交,註冊成功就會看到頁面顯示了:金鑰、密鑰。

金鑰是前端頁面使用,密鑰是後端在 server 使用。
本篇會使用 Google Apps Script 來寫後端。
把這邊的金鑰跟密鑰都記下來,後面幾段都會用到。
忘記了也沒關係,因為 reCAPTCHA 後台 都看得到。
頁面使用 reCAPTCHA 方法 1:直接埋 HTML
參考文件:Automatically render the reCAPTCHA widget
前端頁面要放上「我不是機器人」的那塊有二種方法,第一種就是直接在想放的地方放上 HTML。
首先,頁面上先引用 JS:
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
接著,在想放的地方放上以下的程式碼:
除了 data-sitekey
為必填,其他都是選填,可放可不放,各 data-* 說明如下:
- class 必須為「g-recaptcha」
- data-sitekey,填入「網站金鑰」
- data-theme,亮色或深色樣式:dark、light。預設是 light
- data-size,大小:compact、normal。預設是 normal
- data-callback,驗證完成後要觸發哪個 function,會回傳 g-recaptcha-response token
- data-expired-callback,當驗證過期後會觸發哪個 function
- data-error-callback,當驗證失敗時會觸發哪個 function
data-callback
裡面填的是 function 名稱,會自動帶一組 token,這 token 就是可以送到後端,讓後端拿 token 去跟 reCAPTCHA 做驗證,所以建議是必填。
上面的範例是寫 data-callback="verifyCallback"
因此當使用者打勾後,就會呼 verifyCallback
這個 function,因此我們可以寫:
function verifyCallback(token) { // 把 token 送到後端 }
除了 token,還可以額外送出使用者的 IP,後端需不需要 IP 非必要,但 token 就是必要的。
整個 callback 在本篇的 demo,程式碼如下:
uriGAS
是在 Google Apps Script 部署為網路應用程式後會取得的 URL,後端收 token 並做驗證的部份,會統一寫在後續的段落中。
data-expired-callback
當使用者勾選了「我不是機器人」後,在 callback 中產生的 token 存活時間是 2 分鐘,2 分鐘內必須傳給後端讓後端做驗證。二分鐘一到 token 便會失效,並觸發 data-expired-callback
中我們填寫的 function,打勾的樣式也會變成紅字,像這樣:

頁面使用 reCAPTCHA 方法 2:寫 JavaScript 使用
參考文件:Explicitly render the reCAPTCHA widget
第二種方法,就是先在要放置 reCAPTCHA 的地方,放一個空的 div,接著用 JS 把 reCAPTCHA 的打勾按鈕給塞進去。
首先,一樣要先引用 JS:
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
跟方法 1 不一樣,在引用 JS 時就要先帶入參數,參數一共有 3 個:
- onload:api.js 載入完後要執行哪個 function
- render:固定填 explicit
- hl:語系,非必填,reCAPTCHA 會自動檢測。語系列表
onload
就是 api.js 載入完後要執行哪個 function,要執行的就是在指定的 div 上塞入 reCAPTCHA。程式碼如下:
render
有的參數跟方法 1 中的 data-* 相同,說明如下:
- sitekey,填入「網站金鑰」
- theme,亮色或深色樣式:dark、light。預設是 light
- size,大小:compact、normal。預設是 normal
- callback,驗證完成後要觸發哪個 function,會回傳 g-recaptcha-response token
- expired-callback,當驗證過期後會觸發哪個 function
- error-callback,當驗證失敗時會觸發哪個 function
callback
跟方法 1 的 callback 相同,都是執行把收到的 token 傳給後端做驗證,verifyCallback
這個 function 這邊就不再重寫。
後端向 reCAPTCHA 驗證
參考文件:Verifying the user’s response
我們一開始在註冊好帳號後,除了拿到頁面要的金鑰,也會拿到一組後端用的密鑰。
這組密鑰就是後端向 reCAPTCHA 驗證時會用到的。
前面寫的二種方法,最後都有寫一個 callback 把拿到的 token 傳來,後端要做的,就是把拿到的 token,以及密鑰,POST 到 reCAPTCHA 指定的 URL,就會收到回應,看是成功或失敗。
本篇是拿 Google Apps Script 當後端。
登入 Google 帳號後,到 Google 雲端硬碟,新增一個「Google Apps Script」的檔案:

(以下將 Google Apps Script 簡稱 GAS)
新增後先存檔,接著整個寫程式碼的部份改成以下:
doPost
指的是這個 GAS 收到前端 POST 時要執行的。
var ip = params.ip
、remoteip: ip
這二行,如果前端沒有給 IP 的話就要刪掉。
最後的 return ContentService.create......
就是把從 reCAPTCHA 拿到的結果,回傳給前端。
結果會是 JSON,有的值如下:
{ "success": true|false, "challenge_ts": yyyy-MM-dd'T'HH:mm:ssZZ, "hostname": string, "error-codes": [...] // optional }
success
為 false
的情況目前知道的有二:token 已被用過、token 的存在時間超過 2 分鐘。
目前測大部份都是 true
,有可能判定是機器人時會回傳 false
吧?
取 IP
因為後端在 POST 時可以帶上 IP,因此本篇的 Demo 有取 IP 的部份。
這邊用的是:
https://www.cloudflare.com/cdn-cgi/trace
這個 URL,用 GET 回來時會是字串,裡面就會有 IP 值,用一個 for
回圈把 IP 值給撈出來就行了。
Demo 及原始碼
本篇的 Demo 如下:
https://letswritetw.github.io/letswrite-recaptcha-v2/
方法 1、方法 2、GAS 部份,這三項都有,分成三頁。
頁面上就直接會有「我不是機器人」的區塊。
直接看頁面原始碼可以看到 HTML 及 JS。
或是可以到 Github 上看整個 Demo 的原始碼:
https://github.com/letswritetw/letswrite-recaptcha-v2

