java.awt.image.BufferedImage 是內存中圖片緩衝區,支持直接繪圖、像素操作、多種圖片格式輸出(JPEG/PNG/GIF)。
@WebServlet("/captcha")
public class CaptchaServlet extends HttpServlet {
private static final int WIDTH = 130, HEIGHT = 40;
private static final String CHARS_NUM = "0123456789";
private static final String CHARS_LETTER = "ABCDEFGHJKLMNPQRSTUVWXYZ";
private static final String CHARS_MIX = CHARS_NUM + CHARS_LETTER;
private static final String CHARS_CHINESE = "的了一是我不在人有來他這上..."; // 300+ 常用字
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String type = req.getParameter("type"); // num | letter | mix | chinese | math
if (type == null) type = "mix";
// 1. 創建圖片緩衝區
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
// 2. 繪製背景
g.setColor(Color.WHITE);
g.fillRect(0, 0, WIDTH, HEIGHT);
// 3. 繪製邊框
g.setColor(Color.LIGHT_GRAY);
g.drawRect(0, 0, WIDTH - 1, HEIGHT - 1);
// 4. 繪製幹擾線 (5~8 條)
g.setColor(Color.GRAY);
Random rand = new Random();
for (int i = 0; i < 6; i++) {
int x1 = rand.nextInt(WIDTH), y1 = rand.nextInt(HEIGHT);
int x2 = rand.nextInt(WIDTH), y2 = rand.nextInt(HEIGHT);
g.drawLine(x1, y1, x2, y2);
}
// 5. 繪製幹擾點 (可選)
for (int i = 0; i < 30; i++) {
g.drawOval(rand.nextInt(WIDTH), rand.nextInt(HEIGHT), 1, 1);
}
// 6. 生成驗證碼文本
String code = generateCode(type, rand);
// 7. 繪製文字 (隨機字體、大小、旋轉、顏色)
Font[] fonts = { new Font("微軟雅黑", Font.BOLD, 28), new Font("Arial", Font.BOLD, 28) };
for (int i = 0; i < code.length(); i++) {
g.setFont(fonts[rand.nextInt(fonts.length)]);
g.setColor(new Color(rand.nextInt(100), rand.nextInt(100), rand.nextInt(100)));
int x = 20 + i * 28;
int y = 28;
double theta = (rand.nextInt(30) - 15) * Math.PI / 180; // -15°~15°
g.rotate(theta, x, y);
g.drawString(String.valueOf(code.charAt(i)), x, y);
g.rotate(-theta, x, y);
}
g.dispose();
// 8. 存入 Session (有效期 5 分鐘)
HttpSession session = req.getSession();
session.setAttribute("captcha_code", code);
session.setMaxInactiveInterval(300);
// 9. 輸出圖片 (禁止緩存)
resp.setContentType("image/jpeg");
resp.setHeader("Pragma", "No-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);
ImageIO.write(image, "JPEG", resp.getOutputStream());
}
private String generateCode(String type, Random rand) {
StringBuilder sb = new StringBuilder();
String pool = switch (type) {
case "num" -> CHARS_NUM;
case "letter" -> CHARS_LETTER;
case "chinese" -> CHARS_CHINESE;
case "math" -> { yield generateMathExpression(rand); } // 返回 "12+5=?"
default -> CHARS_MIX;
};
if (!"math".equals(type)) {
for (int i = 0; i < 4; i++) sb.append(pool.charAt(rand.nextInt(pool.length())));
}
return sb.toString();
}
private String generateMathExpression(Random rand) {
int a = rand.nextInt(50) + 1, b = rand.nextInt(50) + 1;
String op = rand.nextBoolean() ? "+" : "-";
int result = "+".equals(op) ? a + b : a - b;
// 存入 session 的是結果
return a + op + b + "="; // 前端顯示 "12+5=",用戶輸入結果
}
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { doGet(req, resp); }
}
<form action="/login" method="post">
<img id="captchaImg" src="/captcha?type=mix" title="點擊刷新" style="cursor:pointer;vertical-align:middle"/>
<input type="text" name="captcha" placeholder="驗證碼" required/>
<button type="submit">登錄</button>
</form>
<script>
document.getElementById('captchaImg').onclick = function() {
this.src = '/captcha?type=mix&t=' + Date.now(); // 時間戳防緩存
};
</script>
@PostMapping("/login")
public String login(@RequestParam String captcha, HttpSession session, Model model) {
String realCode = (String) session.getAttribute("captcha_code");
if (realCode == null || !realCode.equalsIgnoreCase(captcha.trim())) {
model.addAttribute("msg", "驗證碼錯誤");
return "login";
}
session.removeAttribute("captcha_code"); // 一次性失效
// ... 正常登錄邏輯
return "redirect:/home";
}
ImageIO.write(gif, "GIF", out) 需自定義 ImageWriter。SET captcha:sessionId code EX 300。Servlet + BufferedImage 實現驗證碼輕量零依賴,適合中小項目。核心點:**隨機性(字符/顏色/旋轉/幹擾)+ 一次性 Session 存儲 + 禁緩存響應頭**。生產環境建議升級成熟驗證碼服務,防暴力破解、機器識別、重放攻擊。