防止表单恶意提交、暴力破解、机器人爬虫、刷接口攻击。核心流程:生成随机码 → 存 Session → 绘制图片 → 响应输出 → 前端展示 → 用户输入 → 后端校验。
@WebServlet("/VerifyCodeServlet")
public class VerifyCodeServlet extends HttpServlet {
// 配置参数(可在 web.xml init-param 覆盖)
private int width = 120;
private int height = 32;
private int codeCount = 4;
private int fontHeight;
private int codeX, codeY;
// 字符集:去除易混淆字符 (0/O, 1/I/l)
private char[] codeSequence = {
'A','B','C','D','E','F','G','H','J','K','M','N','P','Q','R','S','T','U','V','W','X','Y','Z',
'2','3','4','5','6','7','8','9'
};
@Override
public void init() throws ServletException {
// 从 web.xml 读取配置
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);
// 计算绘制坐标
codeX = (width - 4) / (codeCount + 1);
fontHeight = height - 10;
codeY = height - 7;
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 创建画布
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
Random random = new Random();
// 2. 背景填充
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0, 0, width, height);
// 3. 字体设置
Font font = new Font("Fixedsys", Font.PLAIN, fontHeight);
g.setFont(font);
// 4. 干扰线(16 条)
g.setColor(Color.GRAY);
for (int i = 0; i < 16; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);
}
// 5. 绘制验证码字符
StringBuilder randomCode = new StringBuilder();
for (int i = 0; i < codeCount; i++) {
String ch = String.valueOf(codeSequence[random.nextInt(codeSequence.length)]);
// 随机颜色
g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
g.drawString(ch, (i + 1) * codeX, codeY);
randomCode.append(ch);
}
// 6. 存入 Session(大写统一,校验时忽略大小写)
HttpSession session = req.getSession();
session.setAttribute("validateCode", randomCode.toString().toUpperCase());
// 7. 禁止缓存
resp.setHeader("Pragma", "No-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);
resp.setContentType("image/jpeg");
// 8. 输出图片流
try (ServletOutputStream out = resp.getOutputStream()) {
ImageIO.write(image, "JPEG", out);
}
}
}
<servlet>
<servlet-name>VerifyCodeServlet</servlet-name>
<servlet-class>com.servlet.VerifyCodeServlet</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>32</param-value></init-param>
<init-param><param-name>codeCount</param-name><param-value>4</param-value></init-param>
</servlet>
<servlet-mapping>
<servlet-name>VerifyCodeServlet</servlet-name>
<url-pattern>/VerifyCodeServlet</url-pattern>
</servlet-mapping>
<%@ page pageEncoding="UTF-8"%>
<html><head><title>用户登录</title></head><body>
<form action="login.action" method="post">
用户名: <input name="username" /><br/>
密码: <input name="password" type="password" /><br/>
验证码: <input name="verifyCode" maxlength="4" style="width:60px" />
<img id="captchaImg" src="VerifyCodeServlet"
align="absmiddle" title="看不清?点击刷新" style="cursor:pointer" />
<br/>
<input type="submit" value="登录" />
</form>
<script>
// 点击图片刷新验证码(加随机参数防浏览器缓存)
document.getElementById('captchaImg').onclick = function() {
this.src = 'VerifyCodeServlet?' + Math.random();
};
</script>
</body></html>
public String login() {
String inputCode = request.getParameter("verifyCode");
String sessionCode = (String) session.getAttribute("validateCode");
if (inputCode == null || sessionCode == null) {
addFieldError("verifyCode", "验证码不能为空");
return INPUT;
}
// 忽略大小写校验
if (!inputCode.equalsIgnoreCase(sessionCode)) {
addFieldError("verifyCode", "验证码错误");
return INPUT;
}
// 校验通过,移除验证码防止重用
session.removeAttribute("validateCode");
// 继续业务校验...
return SUCCESS;
}
| 痛点 | 解决方案 |
|---|---|
| 图片验证码用户体验差 | 滑块拼图/点选文字/行为验证(如极验、网易易盾) |
| 分布式集群 Session 不共享 | Redis 存储验证码,Key: captcha:{uuid}, TTL 5分钟 |
| 高并发图片生成性能瓶颈 | 预生成验证码池、异步生成、CDN 缓存静态资源 |
| 安全性不足(OCR 识别) | 增加扭曲、干扰点、字体随机、中文汉字验证码 |