驗證碼圖片 URL 保持不變,瀏覽器會緩存。關鍵技巧:在 URL 後添加隨機查詢參數(如 ?t=timestamp 或 ?v=guid),強制瀏覽器重新請求服務端生成新圖片。
// ASP.NET Core Controller
[HttpGet("captcha")]
public IActionResult GetCaptcha()
{
var code = GenerateRandomCode(4); // 如 "A3F9"
HttpContext.Session.SetString("CaptchaCode", code); // 存 Session/Redis
var imgBytes = DrawCaptchaImage(code); // 生成圖片字節數組
return File(imgBytes, "image/png");
}
private string GenerateRandomCode(int length)
{
const string chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // 排除易混淆字符
var rnd = RandomNumberGenerator.Create();
var bytes = new byte[length];
rnd.GetBytes(bytes);
return new string(bytes.Select(b => chars[b % chars.Length]).ToArray());
}
private byte[] DrawCaptchaImage(string code)
{
using var bmp = new Bitmap(100, 36);
using var g = Graphics.FromImage(bmp);
g.Clear(Color.White);
// 干擾線、噪點、字體渲染...
using var ms = new MemoryStream();
bmp.Save(ms, ImageFormat.Png);
return ms.ToArray();
}
<img id="captchaImg" src="/api/captcha" alt="驗證碼" style="cursor:pointer; vertical-align:middle;" />
<span id="refreshLink" style="margin-left:8px; color:#007bff; cursor:pointer; user-select:none;">看不清?換一張</span>
<script>
(function() {
var img = document.getElementById('captchaImg');
var link = document.getElementById('refreshLink');
function refresh() {
// 關鍵:時間戳防緩存
img.src = '/api/captcha?t=' + Date.now();
}
img.addEventListener('click', refresh);
link.addEventListener('click', refresh);
})();
</script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script>
$(function() {
$('#captchaImg, #refreshLink').on('click', function() {
$('#captchaImg').attr('src', '/api/captcha?v=' + new Date().getTime());
});
});
</script>
<!-- Vue 3 -->
<script setup>
import { ref } from 'vue';
const captchaSrc = ref('/api/captcha');
const refresh = () => { captchaSrc.value = '/api/captcha?t=' + Date.now(); };
</script>
<template>
<img :src="captchaSrc" @click="refresh" alt="驗證碼" />
<button @click="refresh">刷新</button>
</template>
{/* React */}
function Captcha() {
const [src, setSrc] = useState('/api/captcha');
const refresh = () => setSrc('/api/captcha?t=' + Date.now());
return <>
<img src={src} onClick={refresh} alt="驗證碼" />
<button onClick={refresh}>刷新</button>
</>;
}
場景:用戶輸入驗證碼失焦時,前端 AJAX 驗證,錯誤則自動刷新圖片並清空輸入框。
<input type="text" id="txtCaptcha" maxlength="4" placeholder="驗證碼" />
<img id="captchaImg" src="/api/captcha" />
<span id="captchaMsg" class="text-danger"></span>
<script>
document.getElementById('txtCaptcha').addEventListener('blur', async function() {
var code = this.value.trim().toUpperCase();
if (!code) return;
try {
var res = await fetch('/api/validate-captcha', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
});
var result = await res.json();
if (!result.valid) {
// 驗證失敗:刷新圖片、清空輸入、提示
document.getElementById('captchaImg').src = '/api/captcha?t=' + Date.now();
this.value = '';
document.getElementById('captchaMsg').textContent = '驗證碼錯誤,已自動刷新';
} else {
document.getElementById('captchaMsg').textContent = '';
}
} catch (e) {
console.error(e);
}
});
</script>
| 風險 | 對策 |
|---|---|
| 暴力破解 | 同 IP/Session 限制請求頻率(如 5 次/分鐘),失敗鎖定 10 分鐘 |
| 自動識別 (OCR) | 干擾線、扭曲、噪點、隨機字體、點擊/滑動/圖標選擇驗證碼 |
| Session 劫持 | 驗證碼驗證後立即 Session.Remove("CaptchaCode"),一次性使用 |
| 繞過驗證 | 敏感操作(登錄、註冊、支付)雙重驗證:前端 JS + 後端強制校驗 |
| 分佈式部署 | 驗證碼存 Redis(Key: Captcha:{SessionId},TTL 5 分鐘),而非 InProc Session |
CaptchaNet、SkiaSharp 繪製高質量圖形驗證碼。從簡單的 src += '?t=' + Date.now() 到企業級行為驗證,根據業務風險等級選擇適當方案,核心原理始終是「服務端生成、客戶端展示、一次性驗證、防緩存刷新」。