當系統進行前後端分離後,前端與後端的溝通將會以API的形式進行互動,一般在填寫表單驗證或登入動作時,基於安全保護,我們會加入Google recaptcha方法來過濾是否有不良的機器人程式一直try我們的API。
使用情境
使用者在登入頁面時
輸入帳號密碼後
還必須要完成認證碼認證(g-recaptcha)
才能進行本次API資料傳送
流程架構
- 取得/產生
g-recaptcha
驗證區塊(我不是機器人),需透過google_site_key
參數
- g-recaptcha驗證完後會得到
token
,將token
和表單資料一併傳送到API Server
- 驗證token是否正確,會使用到
google_secret_key
- API Server回傳結果
- g-recaptcha token 正確
- g-recaptcha token 驗證失敗 : secret_key錯誤 or token 錯誤
- g-recaptcha token 空值沒填失敗
Client端實作 (HTML + JS)
參考 : Google recaptcha V2 - display
引入g-recaptcha script resource
參考教學有兩種render widget方法
這邊使用js onload callback方式
因為透過js控制 g-captcha會比較靈活
且你的 google_site_key
可以存在js config裡面
不用放在html裡
1
|
<script src='https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit' async defer></script>
|
實作onload event function
grecaptcha.render
可以指令兩種DOM物件載入方法
一種是傳element id
, 另一種是 document.getElementById(xxx)
這方法run成功後
你所指定的div element就會變成 g-recaptch 我不是機器人
1
2
3
4
5
6
7
8
9
|
<div id="g-recaptcha"></div>
<script>
var onloadCallback = function() {
widgetId = grecaptcha.render('g-recaptcha', {
'sitekey' : 'your_google_site_key',
});
};
</script>
|
我們可以透過grecaptcha.getResponse()
的方法
得到使用者驗證完g-recaptcha後的token
1
2
3
4
5
6
7
8
9
10
11
12
|
function getPostdata() {
let data = {};
$('form').find('input').each(function(index, element){
let key = $(element).attr('name');
let value = $(element).val();
data[key] = value;
});
data['google_recaptcha_token'] = grecaptcha.getResponse();
return data;
}
|
POST登入API的json傳送範例大概會像是這樣
記得google_recaptcha_token
的參數名稱需要與後端API一致
1
2
3
4
5
6
|
// Postdata Example
{
"username":"foo",
"password":"secret",
"google_recaptcha_token":"HIUFJEWUIFHEWr32jrojsefodivu"
}
|
API端實作 (Laravel)
參考 : Google recaptcha V2 - verify
考慮到g-recaptcha這個方法可以重複被用在很多地方
所以我使用middleware的方式包裝起來
這個g-recaptcha middleware處理2件事情 :
- required validation : 判斷
google_recaptcha_token
是否為空
- verify token : 驗證
token
是否正確
app/Http/Middleware/GoogleRecapchaV2.php
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
|
<?php
namespace App\Http\Middleware;
use Closure;
use Exception;
use GuzzleHttp\Client;
class GoogleRecapchaV2
{
public function handle($request, Closure $next)
{
/* production 才驗證 recaptcha */
if (config('app.env') === 'production') {
$request->validate([
'google_recaptcha_token' => 'required'
], [
'required' => '請驗證「我不是機器人」'
]);
if (!$this->verify($request->google_recaptcha_token)) {
throw new Exception('g-recaptcha認證失敗');
}
}
return $next($request);
}
private function verify(string $token = null) : bool
{
$url = 'https://www.google.com/recaptcha/api/siteverify';
$postdata = [
'secret' => config('settings.google_recaptcha_secret'),
'response' => $googleRecaptchaToken,
];
$client = new Client();
$response = $client->request('POST', $url, [
'form_params' => $postdata
]);
$code = $response->getStatusCode();
$content = json_decode($response->getBody()->getContents());
if ($code === 200 && $content->success === true) {
return true;
}
return false;
}
}
|
加入新的Middelware變數定義 : g-recaptcha
app/Http/Kernel.php
1
2
3
|
protected $routeMiddleware = [
'g-recaptcha' => \App\Http\Middleware\GoogleRecapchaV2::class,
];
|
middleware方法定義好後
之後你的route若是想加入g-recaptcha
只要套入middelware就好
這樣的做法會比較彈性靈活
app/routes/api.php
1
2
3
4
5
|
<?php
Route::post('/login', 'AuthController@login')->middleware('g-recapcha');
Route::get('/article', 'ArticleController@index');
Route::post('/article', 'ArticleController@store')->middleware('g-recapcha');
|
Summary
以前剛開始使用g-recaptcha的時候
只知道會使用別人寫好的laravel套件
沒有深入研究
後來前後端分離的經驗越來越多之後
慢慢可以釐清整個後端與前端API的傳送流程順序
通常只要牽扯到與第三方API互動時
流程會比較複雜些
建議大家先把API傳送流程架構
畫好後
再來實作每個步驟功能
而且有問題時也可以拿來與有經驗的人討論