用PHP搭配Cloudflare Turnstile CAPTCHA

網路上有許多機器人驗證的替代方案,這裡介紹與 Google reCAPTCHA 寫法幾乎一樣的 Turnstile 給大家嘗試看看。

下面的寫法給各位參考:

後端PHP傳送與接收資料

與 Google 一樣,將query「response」與「secret」丟過去就可以了(範例多了「remoteip」,這個不一定要有)。


// captcha.php
$captcha = $_POST['cf-turnstile-response'];

$secretKey = 'your_secret_key';

$ip = $_SERVER['REMOTE_ADDR'];

$url_path = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
$data = array('secret' => $secretKey, 'response' => $captcha, 'remoteip' => $ip);

$opts = array(
  'http'=> array(
    'method'=> 'POST',
    'content'=> http_build_query($data)
  )
);
$query = stream_context_create($opts);
$result = file_get_contents($url_path, false, $query);
echo json_encode($result);

// 如果你想在後端直接判斷有沒有成功
$array = json_decode($result, true);
$success = $array['success'];
if($success){
  // 驗證成功
}else{
  // 驗證失敗
}

後端PHP這樣寫沒有問題,但資料回傳到前端JavaScript問題就出現了。

前端JavaScript接收Object


$.ajax({
  type: 'POST',
  url: 'captcha.php',
  data: {
    'cf-turnstile-response': $('[name="cf-turnstile-response"]').val()
  },
}).done(function(data) {
  data = JSON.parse(JSON.parse(data).toString());
  if(!data.success){
    alert(data['error-codes'].toString());
  }
})

你會發現我 JSON.parse 了兩次,還 toString() 了一次,為什麼?我們來看正常情況只 JSON.parse 一次會發生什麼事:


// ...
data = JSON.parse(data);

console.log(data);
// {"success":false,"error-codes":["missing-input-response"],"messages":[]}

console.log(data.success);
// undefined

console.log(data[0]);
// '{'

console.log(Object.keys(data).map(k => data[k]));
console.log(Object.values(data));
// 兩種寫法得到的結果一樣
// ['{', '"', 's', 'u', 'c', 'c', 'e', 's', 's', '"', ':', 'f', 'a', 'l', 's', 'e', ',', '"', 'e', 'r', 'r', 'o', 'r', '-', 'c', 'o', 'd', 'e', 's', '"', ':', '[', '"', 'm', 'i', 's', 's', 'i', 'n', 'g', '-', 'i', 'n', 'p', 'u', 't', '-', 'r', 'e', 's', 'p', 'o', 'n', 's', 'e', '"', ']', ',', '"', 'm', 'e', 's', 's', 'a', 'g', 'e', 's', '"', ':', '[', ']', '}']

一開始的 data 看似正常,但是在用 key 去取得 value 的時候卻是這副德行,解決方法就是先 toString() 之後再 JSON.parse 一次。

另一種差不多的寫法

這次我們先在PHP建立回傳前端專用的array,並且在驗證完之後先不要json_encode,回傳array的時候再json_encode:


$message = array();

// ...
$result = file_get_contents($url_path, false, $query);
$message['captcha'] = $result;

echo json_encode($message, true);

JavaScript的寫法就變得單純許多:


// ...
data = JSON.parse(data);
let captcha = JSON.parse(data.captcha);

if(!captcha.success){
  alert(captcha['error-codes'].toString());
  turnstile.reset();
}

希望這些資訊多少有幫助到你。


官方相關資料

留言

這個網誌中的熱門文章

用CSS的 min() max() 與vw,設計有極限值的RWD響應式文字

10 steps、「ライブ会場を沸らせる、フロアを沸かす」ミーム動画の作り方 (Viggle AI)

運用資料層 dataLayer.push 建立 GTM 自訂事件