更新於 2024/03/08閱讀時間約 10 分鐘

[React.js] 來點capcha吧!

有一天早上,我悠哉地端著咖啡打開YT,正享受著每周四早上用「老高與小茉」配早餐的美好時光時,突然收到一封敝公司資安團隊警告信,內容大概是說村莊沒有失火,但我們的系統遭遇自動化機器人攻擊,可能需要增加一點capcha功能。


喔對了,早上10點收到信,中午12點前就要做好上線,主管說前端就簡單拉一下就好很快啦 。


正好之前沒做過,趁這次來記錄一下。


何謂 CAPTCHA?


CAPTCHA 一字,是「全自動區分電腦和人類的公開圖靈測試」的縮寫。

Completely Automated Public Turing test to tell Computers and Humans Apart 

會在網頁上出現奇怪的文字或是圖案要你輸入,也就是俗稱的「驗證碼」。

人類何苦為難人類?

CAPTCHA 的主要目的之一是確保登錄或註冊行為是由真實的人類執行,而不是機器人或其他自動化程式。為了實現這一目的,最簡單的方式就是在登錄或註冊頁面上添加 CAPTCHA 控件,並對其進行調整,增加圖像辨識的難度,從而提高自動化程式通過驗證的難度。


p.s. 近年AI發展快速,已經有相當多研究指出這些AI判斷精準度幾乎超越人類


恩,上面說要做capcha你就做capcha,囉嗦啥呢


這次紀錄的是用React.js做一個capcha 元件,整體邏輯步驟如下:

  1. 後端會給你一字串
  2. 前端把文字畫出來
  3. 做一個輸入框往後面丟

我們開始囉~

首先準備一個react function component,先轉一轉,再泡泡牛奶。

我們畫圖預計使用canvas套件,無論你是裝NPM套件,還是直接用瀏覽器原生的都可以。

但是,可能是我平時沒有燒香拜佛的關係,我選了好幾套npm canvas套件安裝使用都失敗,於是我接下來只能用瀏覽器原生的canvas功能來demo。🤷‍♂️

Level0Capcha

import React, { useEffect, useRef } from "react";
export default function Level0Capcha() {
// 瀏覽器原生canvas需使用ref去關聯渲染
const canvasRef = useRef(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext("2d");
    ctx.font = "48px serif";
    ctx.fillText("5566", 10, 50);
  }, []);

// 使用 canvas tag
  return <canvas ref={canvasRef} width={200} height={100} />;
}
  • 注意 Line:4 瀏覽器原生canvas需使用ref去關聯渲染

成果大概是這樣


雖然看起來跟文字顯示沒什麼兩樣,但其實是張貨真價實的圖,很多年以前比較笨的自動化程式遇到圖片就會不知道如何辨識了。


但是,後來的圖像辨識能力顯著進步,於是我們要對圖片做一點加工,我們要幫它加上一點扭曲與噪點的效果


BasicCapcha

import React, { useEffect, useRef } from "react";
export default function BasicCapcha() {
const canvasRef = useRef(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext("2d");
    ctx.font = "48px serif";
    ctx.fillText("5566", 10, 50);
// 圖片扭曲效果
distort(ctx, canvas);
// 圖片噪點效果
addNoise(ctx, canvas);

  }, []);


  return <canvas ref={canvasRef} width={200} height={100} />;
}

主要是在繪製canvas的時候去呼叫兩個改變效果的function分別是:
distort 圖片扭曲效果

function distort(ctx, canvas) {
const distortionCanvas = document.createElement("canvas");
distortionCanvas.width = canvas.width;
distortionCanvas.height = canvas.height;
const distortionCtx = distortionCanvas.getContext("2d");

const displacement = 5; // 這裡控制扭曲程度
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
const offsetX = Math.round(Math.sin(y / 10) * displacement);
const offsetY = Math.round(Math.cos(x / 10) * displacement);
distortionCtx.drawImage(
canvas,
x,
y,
1,
1,
x + offsetX,
y + offsetY,
1,
1
);
}
}

ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(distortionCanvas, 0, 0);
}

noise 圖片噪點效果

function addNoise(ctx, canvas) {
const noiseDensity = 0.05; // 控制噪點密度
const noiseAmount = 50; // 控制噪點強度

for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
if (Math.random() < noiseDensity) {
const offset = Math.round(
Math.random() * noiseAmount - noiseAmount / 2
);
const pixel = ctx.getImageData(x, y, 1, 1);
const [r, g, b, a] = pixel.data;
ctx.fillStyle = `rgba(${r},${g},${b},${a})`;
ctx.fillRect(x + offset, y + offset, 1, 1);
}
}
}
}

成果大概是這樣



反正我交上去之後,資安團隊跟我說這樣好普通,可不可以再難一點點的? 不要太難,要有點獨特性的。


好吧,我只好用我最喜歡的女團成員做一個...

K-POP Capcha

export default function KPOPCapcha() {
const canvasRef = useRef(null);

useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");

const img = new Image();
img.crossOrigin = "anonymous"; // 這是為了處理跨域圖片
img.onload = () => {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
distort(ctx, canvas);
addNoise(ctx, canvas);
};
img.src =
"https://upload.wikimedia.org/wikipedia/commons/1/12/230601_Karina_%28aespa%29.jpg";
}, []);

return (
<div
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
>
<h1>Login</h1>
<canvas ref={canvasRef} width={200} height={100} />
<h2>{"Please enter the K-POP Singer's name."}</h2>
<TextField />
</div>
);
}


來啊,猜看看這誰啊



分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.