我们将分步实现一个简单的图形验证码,并将其集成到登录页面中。

核心思路
-
生成验证码图片:
- 在服务器端,创建一个
BufferedImage对象,这相当于一张内存中的画布。 - 获取该画布的
Graphics2D对象,这是我们的“画笔”。 - 使用“画笔”在画布上绘制背景色、干扰线、随机字符(验证码)等。
- 将绘制好的图片,通过
response.getOutputStream()写回到客户端的浏览器。
- 在服务器端,创建一个
-
存储验证码:
- 为了后续验证,服务器必须知道它刚刚生成了什么验证码。
- 最简单的存储方式是使用
HttpSession,将生成的验证码字符串存入session中。
-
前端请求与显示:
- 在登录页面的
<img>标签中,将src属性指向 Servlet 的 URL(/captchaServlet)。 - 当页面加载时,浏览器会自动请求这个 Servlet,从而获取并显示验证码图片。
- 在登录页面的
-
用户提交与验证:
(图片来源网络,侵删)- 用户在登录表单中输入用户名、密码和验证码。
- 表单提交到另一个 Servlet(
LoginServlet)。 LoginServlet从request中获取用户输入的验证码,并与session中存储的验证码进行比较。- 比较成功则验证通过,失败则提示错误。
创建生成验证码的 Servlet (CaptchaServlet.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("/captchaServlet")
public class CaptchaServlet extends HttpServlet {
// 验证码字符集
private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置响应内容类型为图片
response.setContentType("image/png");
// 2. 创建一个 BufferedImage 对象作为画布
int width = 120;
int height = 40;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 3. 获取 Graphics2D 对象,作为画笔
Graphics2D g2d = image.createGraphics();
// 4. 绘制背景
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, width, height);
// 5. 绘制干扰线
Random random = new Random();
g2d.setColor(Color.LIGHT_GRAY);
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);
}
// 6. 生成随机验证码
StringBuilder captcha = new StringBuilder();
g2d.setFont(new Font("Arial", Font.BOLD, 24));
for (int i = 0; i < 4; i++) {
char c = CHARACTERS.charAt(random.nextInt(CHARACTERS.length()));
captcha.append(c);
// 设置随机颜色
g2d.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
// 绘制字符,位置稍微错开
g2d.drawString(String.valueOf(c), 25 * i + 10, 28);
}
// 7. 将验证码存入 session
HttpSession session = request.getSession();
session.setAttribute("captcha", captcha.toString());
// 8. 释放资源
g2d.dispose();
// 9. 将图片输出到 response 的输出流中
ImageIO.write(image, "png", response.getOutputStream());
}
}
代码解释:
@WebServlet("/captchaServlet"): 将 Servlet 映射到/captchaServlet这个 URL。response.setContentType("image/png"): 告诉浏览器我们返回的是一个 PNG 图片。BufferedImage: 内存中的图像对象。Graphics2D: Java 2D 绘图 API,用于在图像上绘制各种形状和文本。session.setAttribute("captcha", captcha.toString()): 这是关键一步,将生成的验证码字符串存入session,以便后续验证。ImageIO.write(...): 将BufferedImage对象写入到 HTTP 响应的输出流,浏览器就能显示它了。
创建登录页面 (login.html)
这个页面包含一个表单和一个用于显示验证码的 关键点: 这个 Servlet 接收登录请求,并验证用户输入的验证码。 代码解释: 如果你使用 Maven 构建项目,需要添加 <img>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">登录</title>
<style>
body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f0f2f5; }
.login-box { border: 1px solid #ccc; padding: 20px; border-radius: 8px; background-color: white; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; }
input[type="text"], input[type="password"] { width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; }
.captcha-container { display: flex; align-items: center; }
#captchaImage { margin-left: 10px; cursor: pointer; border: 1px solid #ccc; }
button { width: 100%; padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background-color: #0056b3; }
#errorMsg { color: red; text-align: center; margin-bottom: 10px; }
</style>
</head>
<body>
<div class="login-box">
<h2>用户登录</h2>
<div id="errorMsg" style="display: none;"></div>
<form id="loginForm" action="loginServlet" method="post">
<div class="form-group">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="captcha">验证码:</label>
<div class="captcha-container">
<input type="text" id="captcha" name="captcha" required>
<!-- 点击图片可以刷新验证码 -->
<img id="captchaImage" src="captchaServlet" alt="验证码" title="点击刷新">
</div>
</div>
<button type="submit">登录</button>
</form>
</div>
<script>
// 点击验证码图片时刷新
document.getElementById('captchaImage').addEventListener('click', function() {
// 添加一个时间戳,防止浏览器缓存
this.src = 'captchaServlet?' + new Date().getTime();
});
// 使用 AJAX 提交表单,避免页面刷新
document.getElementById('loginForm').addEventListener('submit', function(event) {
event.preventDefault(); // 阻止表单默认提交行为
const formData = new FormData(this);
const errorMsg = document.getElementById('errorMsg');
fetch('loginServlet', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('登录成功!');
// 登录成功后可以跳转页面
// window.location.href = 'welcome.html';
} else {
errorMsg.textContent = data.message;
errorMsg.style.display = 'block';
// 刷新验证码
document.getElementById('captchaImage').src = 'captchaServlet?' + new Date().getTime();
}
})
.catch(error => {
console.error('Error:', error);
errorMsg.textContent = '登录请求失败,请稍后重试。';
errorMsg.style.display = 'block';
});
});
</script>
</body>
</html>
<img src="captchaServlet">: 这行代码会触发 CaptchaServlet 的 doGet 方法。<img> 标签会刷新图片:this.src = 'captchaServlet?' + new Date().getTime();,添加时间戳是为了防止浏览器使用缓存的旧图片。fetch (AJAX) 提交表单,这样验证失败时,页面不会刷新,用户体验更好。
创建处理登录的 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;
import org.json.JSONObject; // 需要引入 org.json 库
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
String inputCaptcha = request.getParameter("captcha");
HttpSession session = request.getSession();
String sessionCaptcha = (String) session.getAttribute("captcha");
JSONObject jsonResponse = new JSONObject();
// 验证逻辑
if (sessionCaptcha == null || !sessionCaptcha.equalsIgnoreCase(inputCaptcha)) {
// 验证码错误
jsonResponse.put("success", false);
jsonResponse.put("message", "验证码错误!");
} else {
// 验证码正确,这里可以继续验证用户名和密码
// if ("admin".equals(username) && "password".equals(password)) {
// jsonResponse.put("success", true);
// jsonResponse.put("message", "登录成功!");
// } else {
// jsonResponse.put("success", false);
// jsonResponse.put("message", "用户名或密码错误!");
// }
// 为了简化,我们只验证验证码
jsonResponse.put("success", true);
jsonResponse.put("message", "登录成功!");
}
// 验证完成后,无论成功与否,都应从 session 中移除验证码,防止重复使用
session.removeAttribute("captcha");
// 将 JSON 响应写回客户端
response.getWriter().write(jsonResponse.toString());
}
}
response.setContentType("application/json;charset=UTF-8"): 告诉浏览器我们返回的是 JSON 数据。request.getParameter("captcha"): 获取前端表单提交的验证码。session.getAttribute("captcha"): 从 session 中获取之前存储的验证码。equalsIgnoreCase(): 使用不区分大小写的方式比较验证码,这是最佳实践。session.removeAttribute("captcha"): 非常重要! 验证成功或失败后,立即从 session 中删除验证码,这可以防止“重放攻击”,即用户用同一个验证码多次提交。response.getWriter().write(jsonResponse.toString()): 将构建的 JSON 对象发送回前端。
项目结构与依赖
项目结构
your-web-app/
├── src/
│ └── main/
│ ├── java/
│ │ └── yourpackage/
│ │ ├── CaptchaServlet.java
│ │ └── LoginServlet.java
│ └── webapp/
│ ├── login.html
│ └── WEB-INF/
│ └── web.xml (可选,如果使用注解则不需要)
└── pom.xml (如果你使用 Maven)Maven 依赖 (
pom.xml)org.json 库的依赖。<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- JSON 库 -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20251013</version>
</dependency>
</dependencies>
总结与最佳实践
session 中删除,防止被重复使用。
