整體流程
- 前端頁面加載 → 請求
/getSysManageLoginCode 獲取圖片
- 後端生成驗證碼圖片 → 存入 Session/Cookie → 輸出圖片流
- 用戶輸入 → 失焦/提交時請求
/checkimagecode 校驗
- 後端比對 Session/Cookie 中值 → 返回 ok/error
前端頁面 (image.jsp)
<img id="codeValidateImg" onclick="flushValidateCode()"/>
<a href="javascript:flushValidateCode();">換一張</a>
<script>
function flushValidateCode() {
// 加時間戳防緩存
$('#codeValidateImg').attr('src', '${pageContext.request.contextPath}/getSysManageLoginCode?time=' + new Date());
}
function checkImg(code) {
$.get('${pageContext.request.contextPath}/checkimagecode', {validateCode: code}, function(data) {
if (data === 'ok') alert('驗證通過');
else { alert('驗證碼錯誤'); flushValidateCode(); }
});
}
</script>
後端控制器
驗證碼生成核心類 (RandomValidateCode)
public class RandomValidateCode {
private static final String CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final int WIDTH = 80, HEIGHT = 26, LINE_COUNT = 40, CHAR_COUNT = 4;
public void getRandcode(HttpServletRequest request, HttpServletResponse response, String key) {
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_BGR);
Graphics g = image.getGraphics();
// 1. 填充背景
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 18));
g.setColor(getRandColor(110, 133));
// 2. 畫干擾線
for (int i = 0; i <= LINE_COUNT; i++) drowLine(g);
// 3. 畫隨機字符
StringBuilder code = new StringBuilder();
for (int i = 1; i <= CHAR_COUNT; i++) code.append(drowString(g, i));
// 4. 存儲驗證碼(二選一)
// Cookie 方式(不安全,易被竊取)
response.addCookie(new Cookie(key, code.toString()));
// Session 方式(推薦)
request.getSession().setAttribute(key, code.toString());
// 5. 輸出圖片流
g.dispose();
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
ImageIO.write(image, "png", out);
response.setContentLength(out.size());
response.getOutputStream().write(out.toByteArray());
} catch (IOException e) { e.printStackTrace(); }
}
private Color getRandColor(int fc, int bc) { ... }
private String drowString(Graphics g, int i) { ... }
private void drowLine(Graphics g) { ... }
}
安全性升級建議
- 棄用 Cookie 存儲:驗證碼暴露在客戶端,必須用 Session 或 Redis
- 大小寫不敏感:
equalsIgnoreCase 提升用戶體驗
- 限流與過期:同一 IP 限制請求頻率,驗證碼 60 秒過期
- 防暴力破解:連續錯誤 N 次鎖定賬號/IP,需圖形驗證碼+短信雙因子
- 生產環境:建議接入成熟方案(如 Google reCAPTCHA、hCaptcha、滑塊驗證)