防止機器人自動提交表單,需生成含干擾線、噪點、扭曲字符的圖片驗證碼,並能在伺服器端驗證使用者輸入。
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.imageio.ImageIO;
import java.io.OutputStream;
public class CaptchaUtil {
private static final String CHARS = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
private static final int WIDTH = 120, HEIGHT = 40;
private static final int CODE_LENGTH = 4;
private static final int LINE_COUNT = 5;
private static final Random RANDOM = new Random();
// 生成驗證碼圖片並輸出到 OutputStream (如 HttpServletResponse.getOutputStream())
public static String generate(OutputStream out) throws Exception {
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
// 1. 背景
g.setColor(getRandomColor(200, 250));
g.fillRect(0, 0, WIDTH, HEIGHT);
// 2. 干擾線
for (int i = 0; i < LINE_COUNT; i++) {
g.setColor(getRandomColor(100, 200));
int x1 = RANDOM.nextInt(WIDTH);
int y1 = RANDOM.nextInt(HEIGHT);
int x2 = RANDOM.nextInt(WIDTH);
int y2 = RANDOM.nextInt(HEIGHT);
g.drawLine(x1, y1, x2, y2);
}
// 3. 噪點
for (int i = 0; i < 50; i++) {
g.setColor(getRandomColor(100, 200));
int x = RANDOM.nextInt(WIDTH);
int y = RANDOM.nextInt(HEIGHT);
g.drawOval(x, y, 1, 1);
}
// 4. 驗證碼字符
StringBuilder code = new StringBuilder();
Font font = new Font("Consolas", Font.BOLD, 28);
g.setFont(font);
for (int i = 0; i < CODE_LENGTH; i++) {
char c = CHARS.charAt(RANDOM.nextInt(CHARS.length()));
code.append(c);
g.setColor(getRandomColor(20, 100));
// 隨機旋轉角度
AffineTransform orig = g.getTransform();
g.rotate(RANDOM.nextDouble() * 0.4 - 0.2, 20 * i + 15, 25);
g.drawString(String.valueOf(c), 20 * i + 10, 30);
g.setTransform(orig);
}
g.dispose();
ImageIO.write(image, "PNG", out);
return code.toString();
}
private static Color getRandomColor(int min, int max) {
int r = min + RANDOM.nextInt(max - min);
int g = min + RANDOM.nextInt(max - min);
int b = min + RANDOM.nextInt(max - min);
return new Color(r, g, b);
}
}
@WebServlet("/captcha")
public class CaptchaServlet extends HttpServlet {
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("image/png");
resp.setHeader("Pragma", "no-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);
try (OutputStream out = resp.getOutputStream()) {
String code = CaptchaUtil.generate(out);
req.getSession().setAttribute("captcha_code", code); // 存入 Session
}
}
}
// HTML
// JS 驗證 (提交前)
$('#form').submit(function() {
var input = $('#captchaInput').val().toUpperCase();
// 通常送往後端驗證,這裡僅示範前端邏輯
$.post('/verify', {captcha: input}, function(res) {
if (!res.valid) { alert('驗證碼錯誤'); return false; }
});
});
@PostMapping("/verify")
public Map<String, Boolean> verify(@RequestParam String captcha, HttpSession session) {
String expected = (String) session.getAttribute("captcha_code");
boolean valid = expected != null && expected.equalsIgnoreCase(captcha);
if (valid) session.removeAttribute("captcha_code"); // 一次性使用
return Map.of("valid", valid);
}
Graphics2D.draw(Shape) 繪製貝茲曲線路徑。