我们将使用 JDK 内置的 java.awt 和 java.imageio 库来生成图片,这样就不需要引入第三方依赖,非常适合学习和理解原理。

实现步骤概览
- 创建 Servlet 类: 编写一个 Servlet,用于生成验证码图片并发送到客户端。
- 生成随机验证码字符串: 生成一个随机字符串(如数字、字母组合)。
- 将验证码存入 Session: 将生成的字符串存入
HttpSession中,以便后续在登录等操作中进行验证。 - 绘制验证码图片:
- 创建一个内存中的
BufferedImage画布。 - 设置背景色。
- 绘制干扰线。
- 绘制随机字符。
- 添加干扰点。
- 可能的话,进行扭曲处理,增加识别难度。
- 创建一个内存中的
- 将图片输出到客户端: 使用
ImageIO将BufferedImage对象以 PNG 或 JPEG 格式输出到HttpServletResponse的输出流中。 - 在 JSP 页面中调用: 创建一个 JSP 页面,使用
<img>标签的src属性来请求这个 Servlet,从而显示验证码。 - 编写验证逻辑: 在处理用户登录的 Servlet 中,从 Session 中取出验证码,并与用户提交的验证码进行比较。
第 1 步:创建生成验证码的 Servlet (ImageCodeServlet.java)
这个 Servlet 的唯一任务就是生成一张验证码图片。
import javax.imageio.ImageIO;
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.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
@WebServlet("/imageCode") // 使用注解映射 Servlet 访问路径
public class ImageCodeServlet extends HttpServlet {
// 验证码字符集 (可选的字符)
private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
private static final int CODE_LENGTH = 4; // 验证码长度
private static final int WIDTH = 120; // 图片宽度
private static final int HEIGHT = 40; // 图片高度
private static final int FONT_SIZE = 25; // 字体大小
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 生成随机验证码
String code = generateCode(CODE_LENGTH);
// 2. 将验证码存入 Session
HttpSession session = req.getSession();
session.setAttribute("imageCode", code); // key 为 "imageCode",value 为生成的字符串
// 3. 创建图片画布
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
// 4. 绘制背景
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, WIDTH, HEIGHT);
// 5. 绘制干扰线
drawRandomLines(g2d);
// 6. 绘制验证码字符
drawRandomCharacters(g2d, code);
// 7. 释放资源
g2d.dispose();
// 8. 设置响应头,禁止浏览器缓存图片
resp.setHeader("Pragma", "No-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);
// 9. 将图片以 "image/png" 格式输出到客户端
resp.setContentType("image/png");
ImageIO.write(image, "png", resp.getOutputStream());
}
/**
* 生成指定长度的随机字符串
*/
private String generateCode(int length) {
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(CHARACTERS.charAt(random.nextInt(CHARACTERS.length())));
}
return sb.toString();
}
/**
* 绘制干扰线
*/
private void drawRandomLines(Graphics2D g2d) {
Random random = new Random();
g2d.setColor(getRandomColor(160, 200));
for (int i = 0; i < 8; i++) {
int x1 = random.nextInt(WIDTH);
int y1 = random.nextInt(HEIGHT);
int x2 = random.nextInt(WIDTH);
int y2 = random.nextInt(HEIGHT);
g2d.drawLine(x1, y1, x2, y2);
}
}
/**
* 绘制验证码字符
*/
private void drawRandomCharacters(Graphics2D g2d, String code) {
Random random = new Random();
int x = 15;
int y = HEIGHT / 2 + FONT_SIZE / 2;
for (int i = 0; i < code.length(); i++) {
// 随机字体样式
Font font = new Font("Arial", Font.BOLD | Font.ITALIC, FONT_SIZE);
g2d.setFont(font);
// 随机颜色
g2d.setColor(getRandomColor(20, 130));
// 随机旋转角度 (-30 到 30 度)
int angle = random.nextInt(60) - 30;
double theta = angle * Math.PI / 180;
// 保存当前状态
g2d.save();
// 平移和旋转
g2d.translate(x, y);
g2d.rotate(theta);
// 绘制字符
g2d.drawString(String.valueOf(code.charAt(i)), 0, 0);
// 恢复状态
g2d.restore();
x += WIDTH / CODE_LENGTH;
}
}
/**
* 生成随机颜色
*/
private Color getRandomColor(int min, int max) {
Random random = new Random();
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);
}
}
第 2 步:创建 JSP 页面 (login.jsp)
这个页面包含一个表单,用于用户输入用户名、密码和验证码,关键在于 JSP 关键点说明: 当用户填写完表单并点击登录后,数据会提交到这个 Servlet,这里的核心逻辑是验证码校验。 LoginServlet 关键点说明: 安全性: Session 管理: 验证码必须存储在 Session 中,确保你的应用正确配置了 Session。 字符集: 在 依赖: 这个实现完全基于 JDK 标准库,无需任何额外依赖,非常适合快速集成和原型开发。 通过以上三个步骤,你就完整地实现了一个在 Java Web 应用中生成和验证验证码的功能。<img>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>登录页面</title>
<style>
.code-container {
display: flex;
align-items: center;
}
#imageCode {
margin-left: 10px;
cursor: pointer;
}
</style>
</head>
<body>
<h2>用户登录</h2>
<form action="login" method="post">
用户名: <input type="text" name="username"><br><br>
密码: <input type="password" name="password"><br><br>
验证码:
<div class="code-container">
<input type="text" name="userCode" maxlength="4" style="width: 80px;">
<img id="imageCode" src="imageCode" title="点击刷新" alt="验证码图片">
</div>
<br><br>
<input type="submit" value="登录">
</form>
<script>
// 点击图片刷新验证码
document.getElementById("imageCode").onclick = function() {
// 加上时间戳,防止浏览器缓存图片导致不刷新
this.src = "imageCode?" + new Date().getTime();
};
</script>
</body>
</html>
<img src="imageCode">: 这里的 src="imageCode" 正好对应了我们 ImageCodeServlet 上的 @WebServlet("/imageCode") 注解,浏览器会向这个 URL 发起 GET 请求,Servlet 就会返回图片流。<script> 部分: 为了提升用户体验,我们添加了点击图片刷新验证码的功能。? + new Date().getTime() 是一个常用技巧,可以强制浏览器不使用缓存的图片,确保每次请求都是新的验证码。
第 3 步:创建处理登录的 Servlet (
LoginServlet.java)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.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 设置请求和响应编码
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
// 2. 获取用户输入
String username = req.getParameter("username");
String password = req.getParameter("password");
String userCode = req.getParameter("userCode");
// 3. 从 Session 中获取服务器生成的验证码
HttpSession session = req.getSession();
String serverCode = (String) session.getAttribute("imageCode");
// 4. 验证码校验 (不区分大小写)
if (serverCode == null || !serverCode.equalsIgnoreCase(userCode)) {
// 验证码错误,返回登录页面并提示
req.setAttribute("errorMsg", "验证码错误!");
req.getRequestDispatcher("login.jsp").forward(req, resp);
return; // 终止后续操作
}
// 5. 验证码正确,继续验证用户名和密码
// ... 这里是你的业务逻辑,比如查询数据库 ...
if ("admin".equals(username) && "123456".equals(password)) {
resp.getWriter().println("<h1>登录成功!欢迎 " + username + "</h1>");
} else {
req.setAttribute("errorMsg", "用户名或密码错误!");
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
}
}

session.getAttribute("imageCode"): 从 Session 中取出我们之前存入的验证码。equalsIgnoreCase(userCode): 使用 equalsIgnoreCase 进行比较,这样用户输入时就不区分大小写了,提升了用户体验。forward: 验证码错误时,通常使用 request.getRequestDispatcher("login.jsp").forward(req, resp) 将请求转发回登录页面,而不是重定向 (redirect),这样可以带上错误信息(通过 request.setAttribute)并保留用户之前输入的用户名和密码(如果表单中有设置 value 属性)。session.removeAttribute("imageCode");。
总结与注意事项
java.awt 生成的验证码相对简单,容易被 OCR(光学字符识别)程序识别,对于安全性要求极高的场景,可以考虑:
CHARACTERS 字符串中,我移除了容易混淆的字符,如 0/O, 1/I/l 等,这可以减少用户输入错误。
