Java Web 开发中的图形验证码实现方案
在构建 Web 应用时,为了防止脚本程序恶意刷单、自动注册或暴力破解账号,通常需要在关键交互环节加入图形验证码。这种机制通过要求用户识别特定的字符或图像来区分人类与机器。当然,考虑到用户体验,验证码的复杂度需要适度平衡。本文将演示如何在传统的 Java Web 体系中,利用 Servlet 生成动态图片并结合 JSP 前端完成验证流程。
1. 页面交互设计 (front-end)
前端主要承担展示图片和接收用户输入的任务。为了提升体验,当用户无法看清当前图片时,应提供一键刷新功能。以下是一个简化后的登录表单页面,使用了标准的 HTML 结构和内联脚本处理图片更新请求。
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>安全验证页</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.form-group { margin-bottom: 10px; }
</style>
<script type="text/javascript">
function refreshVerificationImage() {
const imgElement = document.getElementById("verifyPic");
const timeStmp = new Date().getTime();
// 添加时间戳强制浏览器忽略缓存
imgElement.src = "${pageContext.request.contextPath}/api/captcha/gen?t=" + timeStmp;
document.getElementById("inputCode").value = "";
document.getElementById("inputCode").focus();
}
</script>
</head>
<body>
<center>
<h2>身份验证</h2>
<form action="${pageContext.request.contextPath}/api/captcha/check" method="post">
<div class="form-group">
请输入验证码:<input type="text" id="inputCode" name="userInput"/>
</div>
<div class="form-group">
<img id="verifyPic" src="${pageContext.request.contextPath}/api/captcha/gen" onclick="refreshVerificationImage()" />
<a href="javascript:void(0)" onclick="refreshVerificationImage()">点击换一张</a>
</div>
<div>
<button type="submit">提交验证</button>
</div>
</form>
</center>
</body>
</html>
2. 后端图片生成逻辑 (backend-generator)
为了增加识别难度,生成的验证码不应是简单的文字。我们需要使用 Java 的 AWT 库创建 BufferedImage 对象,绘制背景、干扰线以及随机字符。生成的字符串需要暂存到用户的 Session 中,以便后续比对。
import java.io.IOException;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.imageio.ImageIO;
import java.util.Random;
@WebServlet("/api/captcha/gen")
public class CaptchaGenerator extends HttpServlet {
private static final int WIDTH = 120;
private static final int HEIGHT = 40;
private static final String CHAR_POOL = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置响应类型,告知浏览器这是图片资源
resp.setContentType("image/jpeg");
// 禁止浏览器缓存验证码图片
resp.setHeader("Pragma", "no-cache");
BufferedImage bufferImg = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics g = bufferImg.getGraphics();
// 1. 绘制随机颜色背景
Random rand = new Random();
g.setColor(new Color(rand.nextInt(200) + 50, rand.nextInt(200) + 50, rand.nextInt(200) + 50));
g.fillRect(0, 0, WIDTH, HEIGHT);
// 2. 绘制干扰线条 (可选)
for (int i = 0; i < 10; i++) {
g.setColor(new Color(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255)));
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);
}
// 3. 绘制随机字符
StringBuilder correctCode = new StringBuilder();
g.setFont(new Font("Times New Roman", Font.BOLD, 24));
for (int i = 0; i < 4; i++) {
g.setColor(new Color(rand.nextInt(100), rand.nextInt(200), rand.nextInt(200)));
String charStr = String.valueOf(CHAR_POOL.charAt(rand.nextInt(CHAR_POOL.length())));
g.drawString(charStr, 20 + i * 25, 28);
correctCode.append(charStr);
}
// 释放绘图资源
g.dispose();
// 将正确码存入 Session,注意大小写敏感配置
req.getSession().setAttribute("session_captcha", correctCode.toString().toLowerCase());
// 输出流写入图片数据
ImageIO.write(bufferImg, "jpg", resp.getOutputStream());
}
}
3. 后端验证逻辑 (backend-validator)
当用户提交表单后,系统需要将用户输入的字符串与 Session 中存储的原始验证码进行对比。为了提高安全性,通常建议验证失败后立即销毁该次验证码,防止重放攻击。
@WebServlet("/api/captcha/check")
public class CaptchaChecker extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
res.setCharacterEncoding("UTF-8");
res.setContentType("text/html;charset=UTF-8");
String inputVal = req.getParameter("userInput");
String storedVal = (String) req.getSession().getAttribute("session_captcha");
PrintWriter out = res.getWriter();
if (storedVal != null && storedVal.equalsIgnoreCase(inputVal)) {
// 验证成功,可跳转至登录页面或返回 JSON 成功标志
// 成功后可选择清除 session 中的验证码
req.getSession().removeAttribute("session_captcha");
out.println("<h3><font color='green'>验证通过,系统将继续处理请求</font></h3>");
// out.println(jsonResponse(true));
} else {
// 验证失败,提示错误
out.println("<h3><font color='red'>验证码输入错误,请重新尝试</font></h3>");
// 强制刷新验证码链接引导
out.println("<a href='/api/captcha/gen'>生成新验证码</a>");
}
}
}
上述代码完成了从图片绘制到结果比对的完整闭环。在实际生产环境中,还可以引入数学算术题、滑块拼图等更复杂的算法,或者使用第三方云服务来进一步降低被 OCR 技术攻破的风险。