核心 Servlet:ValidateCodeServlet
package com.servlet;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.*;
import javax.servlet.http.*;
public class ValidateCodeServlet extends HttpServlet {
private int width = 100;
private int height = 35;
private int codeCount = 4;
private int fontHeight;
private int codeY;
private final char[] codeSequence = {
'A','B','C','D','E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y','Z',
'2','3','4','5','6','7','8','9' // 去除易混淆 0/O, 1/I
};
@Override
public void init() throws ServletException {
String w = getInitParameter("width");
String h = getInitParameter("height");
String c = getInitParameter("codeCount");
if (w != null) width = Integer.parseInt(w);
if (h != null) height = Integer.parseInt(h);
if (c != null) codeCount = Integer.parseInt(c);
fontHeight = height - 4;
codeY = height - 6;
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
Random rand = new Random();
// 背景
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
// 字體
Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
g.setFont(font);
// 邊框
g.setColor(Color.BLACK);
g.drawRect(0, 0, width-1, height-1);
// 干擾線
g.setColor(Color.GRAY);
for (int i = 0; i < 50; i++) {
int x = rand.nextInt(width);
int y = rand.nextInt(height);
int xl = rand.nextInt(12);
int yl = rand.nextInt(12);
g.drawLine(x, y, x+xl, y+yl);
}
// 生成驗證碼並繪製
StringBuilder code = new StringBuilder();
int charWidth = width / (codeCount + 1);
for (int i = 0; i < codeCount; i++) {
char c = codeSequence[rand.nextInt(codeSequence.length)];
code.append(c);
g.setColor(new Color(rand.nextInt(150), rand.nextInt(150), rand.nextInt(150)));
g.drawString(String.valueOf(c), (i+1)*charWidth, codeY);
}
// 存入 Session
HttpSession session = req.getSession();
session.setAttribute("validateCode", code.toString());
// 防快取
resp.setHeader("Pragma", "No-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);
resp.setContentType("image/jpeg");
// 輸出
ServletOutputStream out = resp.getOutputStream();
ImageIO.write(img, "jpeg", out);
out.close();
}
}
web.xml 註冊
<servlet>
<servlet-name>ValidateCodeServlet</servlet-name>
<servlet-class>com.servlet.ValidateCodeServlet</servlet-class>
<init-param>
<param-name>width</param-name>
<param-value>120</param-value>
</init-param>
<init-param>
<param-name>height</param-name>
<param-value>40</param-value>
</init-param>
<init-param>
<param-name>codeCount</param-name>
<param-value>4</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>ValidateCodeServlet</servlet-name>
<url-pattern>/validateCode</url-pattern>
</servlet-mapping>
前端整合:login.jsp
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<script>
function refreshCode() {
document.getElementById('captchaImg').src = '/validateCode?t=' + Math.random();
}
</script>
</head>
<body>
<form action="/login" method="post">
用戶名:<input name="username" /><br/>
密碼:<input type="password" name="password" /><br/>
驗證碼:
<img id="captchaImg" src="/validateCode" onclick="refreshCode()" alt="點擊刷新" style="cursor:pointer; vertical-align:middle;" />
<input name="captcha" size="6" /><br/>
<input type="submit" value="登入" />
</form>
</body>
</html>
後端驗證邏輯
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
@RequestParam String captcha,
HttpSession session) {
String realCode = (String) session.getAttribute("validateCode");
if (realCode == null || !realCode.equalsIgnoreCase(captcha)) {
model.addAttribute("error", "驗證碼錯誤");
return "login";
}
session.removeAttribute("validateCode"); // 一次性使用
// 繼續登入邏輯...
return "redirect:/home";
}
增強建議
- 干擾點:在干擾線循環中加入
g.fillOval(x, y, 2, 2)。
- 字符旋轉:
g.rotate(theta, cx, cy) 繪製後旋回,大幅提高 OCR 識別難度。
- 字體隨機:準備多種字體數組隨機選取。
- 使用成熟庫:
kaptcha、simple-captcha、patchca 提供更豐富的干擾策略。
- 分布式 Session:集群環境需共享 Session(Redis/Memcached)或改用 JWT 存儲驗證碼。