核心類:CertPicGenerator
package pic;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
import javax.imageio.ImageIO;
public class CertPicGenerator {
// 可用字符集(去除易混淆字符)
private static final char[] CHAR_SET = {
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'0','1','2','3','4','5','6','7','8','9'
};
private static final int DEFAULT_WIDTH = 120;
private static final int DEFAULT_HEIGHT = 40;
private static final int CODE_LENGTH = 4;
private static final int INTERFERE_LINES = 20;
private static final int INTERFERE_DOTS = 50;
public String generate(OutputStream os, int width, int height) throws IOException {
if (width <= 0) width = DEFAULT_WIDTH;
if (height <= 0) height = DEFAULT_HEIGHT;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
// 背景
g.setColor(new Color(0xF0F0F0));
g.fillRect(0, 0, width, height);
// 邊框
g.setColor(Color.GRAY);
g.drawRect(0, 0, width - 1, height - 1);
// 干擾線
Random rand = new Random();
for (int i = 0; i < INTERFERE_LINES; i++) {
g.setColor(new Color(rand.nextInt(200), rand.nextInt(200), rand.nextInt(200)));
int x1 = rand.nextInt(width);
int y1 = rand.nextInt(height);
int x2 = rand.nextInt(width);
int y2 = rand.nextInt(height);
g.drawLine(x1, y1, x2, y2);
}
// 干擾點
for (int i = 0; i < INTERFERE_DOTS; i++) {
g.setColor(new Color(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255)));
g.fillOval(rand.nextInt(width), rand.nextInt(height), 2, 2);
}
// 生成驗證碼並繪製
StringBuilder code = new StringBuilder();
Font font = new Font("Arial", Font.BOLD | Font.ITALIC, height - 8);
g.setFont(font);
int charWidth = (width - 10) / CODE_LENGTH;
for (int i = 0; i < CODE_LENGTH; i++) {
char c = CHAR_SET[rand.nextInt(CHAR_SET.length)];
code.append(c);
g.setColor(new Color(rand.nextInt(100), rand.nextInt(100), rand.nextInt(100)));
// 隨機旋轉角度
double theta = (rand.nextDouble() - 0.5) * 0.6; // -0.3 ~ 0.3 rad
g.rotate(theta, 10 + i * charWidth + charWidth/2, height/2);
g.drawString(String.valueOf(c), 10 + i * charWidth, height/2 + 5);
g.rotate(-theta, 10 + i * charWidth + charWidth/2, height/2);
}
g.dispose();
ImageIO.write(image, "JPEG", os);
return code.toString();
}
}
JSP 端點:captcha.jsp
<%@ page contentType="image/jpeg" import="pic.CertPicGenerator" %>
<%
CertPicGenerator gen = new CertPicGenerator();
String code = gen.generate(response.getOutputStream(), 0, 0);
session.setAttribute("captchaCode", code); // 存入 Session
response.getOutputStream().flush();
%>
登入頁面整合:login.jsp
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<title>登入</title>
<script>
function refreshCaptcha() {
var img = document.getElementById('captchaImg');
img.src = 'captcha.jsp?t=' + Math.random(); // 防快取
}
</script>
</head>
<body>
<form action="loginCheck.jsp" method="post">
用戶名:<input type="text" name="username" /><br/>
密碼:<input type="password" name="password" /><br/>
驗證碼:
<img id="captchaImg" src="captcha.jsp" onclick="refreshCaptcha()" alt="點擊換一張" style="cursor:pointer; vertical-align:middle;" />
<input type="text" name="captchaInput" size="6" /><br/>
<input type="submit" value="登入" />
</form>
</body>
</html>
後端驗證:loginCheck.jsp
<%
String input = request.getParameter("captchaInput");
String real = (String) session.getAttribute("captchaCode");
if (real != null && real.equalsIgnoreCase(input)) {
out.println("驗證碼正確,繼續登入邏輯...");
// 登入成功後清除驗證碼
session.removeAttribute("captchaCode");
} else {
out.println("驗證碼錯誤,請重試。<a href='login.jsp'>返回</a>");
}
%>
關鍵增強點
- 字符集優化:移除 0/O, 1/I, 2/Z 等易混淆字符。
- 干擾線+干擾點+隨機旋轉+隨機顏色:大幅提高機器識別難度。
- Session 存儲:驗證碼僅保存在 Session,不暴露於前端。
- 點擊刷新:前端加時間戳防止瀏覽器快取。
- 忽略大小寫驗證:提升用戶體驗。
生產環境建議
- 使用成熟庫(如
kaptcha, simple-captcha)替代手寫實現。
- 考慮滑塊、點選、行為分析等更友好的驗證方式。
- 加入請求頻率限制,防止暴力破解驗證碼接口。
- 驗證碼圖片響應頭設定
Cache-Control: no-store。