在 Java Web 项目中优雅地实现验证码拦截与校验
在 Java Web 项目中优雅地实现验证码拦截与校验
在 Web 开发中,很多网站都会对敏感页面或公告信息做 验证码拦截,以防止爬虫或恶意访问。
比如访问某些公告详情页时,必须先输入验证码才能继续浏览。
本文将以一个实际的案例为例,演示如何在 Java Web 项目中优雅地实现 Filter 拦截 + 验证码校验 + 友好页面展示。
1. 需求分析
假设我们的系统中有以下目录需要保护:
xzbgg、zhzzsgg、zhzzbhxr、zhzbjggs、zhzzzgg、
xfzbgg、zhfcghxr、zhfcgjg、zhfzzgg、zhzbxx
拦截逻辑:
/xxx/index.jhtml
这样的首页不需要拦截;/xxx/12345.jhtml
这样的详情页必须输入验证码才能访问;- 验证码通过后,自动跳转回用户最初请求的页面。
2. Filter 拦截实现
首先我们定义一个 CaptchaFilter
,利用 正则表达式 精准拦截需要验证的页面:
public class CaptchaFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse resp = (HttpServletResponse) response;String uri = req.getRequestURI();// 定义需要保护的路径前缀String[] protectedPaths = {"xzbgg", "zhzzsgg", "zhzzbhxr", "zhzbjggs", "zhzzzgg","xfzbgg", "zhfcghxr", "zhfcgjg", "zhfzzgg", "zhzbxx"};// 匹配 /xxx/数字.jhtml 的请求String pattern = MessageFormat.format(".*/(?:{0})/\\d+\\.jhtml$", String.join("|", protectedPaths));if (uri.matches(pattern)) {HttpSession session = req.getSession();Boolean captchaPassed = (Boolean) session.getAttribute("captchaPassed");// 如果验证码已通过,放行并清理标记if (Boolean.TRUE.equals(captchaPassed)) {chain.doFilter(request, response);session.removeAttribute("captchaPassed");return;}// 保存目标 URL,便于校验成功后跳转回来session.setAttribute("targetUrl", uri);// 转发到验证码页面req.getRequestDispatcher("/captcha.html").forward(req, resp);return;}// 其他请求直接放行chain.doFilter(request, response);}
}
这样,所有访问 /xxx/数字.jhtml
的请求都会被拦截,并跳转到验证码页面。
3. web.xml 配置
接着在 web.xml
中注册 Filter 和两个 Servlet:
- 一个生成验证码图片(如
JCaptchaServlet
); - 一个校验验证码输入(我们自定义的
CaptchaCheckServlet
)。
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"version="3.1"><!-- 验证码拦截器 --><filter><filter-name>CaptchaFilter</filter-name><filter-class>com.example.filter.CaptchaFilter</filter-class></filter><filter-mapping><filter-name>CaptchaFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 验证码图片生成 Servlet (例如 JCaptcha) --><servlet><servlet-name>CaptchaServlet</servlet-name><servlet-class>com.octo.captcha.module.servlet.image.SimpleImageCaptchaServlet</servlet-class></servlet><servlet-mapping><servlet-name>CaptchaServlet</servlet-name><url-pattern>/captcha.svl</url-pattern></servlet-mapping><!-- 验证码校验 Servlet --><servlet><servlet-name>CaptchaCheckServlet</servlet-name><servlet-class>com.example.servlet.CaptchaCheckServlet</servlet-class></servlet><servlet-mapping><servlet-name>CaptchaCheckServlet</servlet-name><url-pattern>/captcha/check</url-pattern></servlet-mapping>
</web-app>
4. CaptchaCheckServlet 校验逻辑
CaptchaCheckServlet
用来接收用户输入的验证码,并进行校验。
如果校验失败,重定向回 captcha.html?error=1
。
public class CaptchaCheckServlet extends HttpServlet {private ImageCaptchaService captchaService;@Overridepublic void init() throws ServletException {super.init();WebApplicationContext appCtx =WebApplicationContextUtils.getWebApplicationContext(getServletContext());captchaService = BeanFactoryUtils.beanOfTypeIncludingAncestors(appCtx, ImageCaptchaService.class);if (captchaService == null) {throw new ServletException("CaptchaService 未初始化,请检查 Spring 配置");}}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {HttpSession session = req.getSession();String captchaId = session.getId();String inputCode = req.getParameter("code");boolean passed;try {passed = captchaService.validateResponseForID(captchaId, inputCode);} catch (Exception e) {passed = false;}if (passed) {// 验证成功,设置标记并跳转回原页面session.setAttribute("captchaPassed", true);String targetUrl = (String) session.getAttribute("targetUrl");session.removeAttribute("targetUrl");if (targetUrl != null) {resp.sendRedirect(targetUrl);} else {resp.sendRedirect(req.getContextPath() + "/");}} else {// 验证失败,重定向并附带错误标记resp.sendRedirect(req.getContextPath() + "/captcha.html?error=1");}}
}
5. 验证码页面(HTML)
验证码页面保留为 纯 HTML,通过 JavaScript 获取 URL 参数 error
来显示错误提示。
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>请输入验证码</title><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>body {font-family: "Microsoft YaHei", Arial, sans-serif;display: flex;justify-content: center;align-items: center;min-height: 100vh;margin: 0;background: #f4f6f9;}.captcha-container {background: #fff;padding: 2rem;border-radius: 12px;box-shadow: 0 4px 12px rgba(0,0,0,0.1);text-align: center;width: 300px;}h2 {margin-bottom: 1.2rem;font-size: 1.2rem;color: #333;}.error {color: red;margin-bottom: 1rem;display: none;}img {cursor: pointer;margin-bottom: 1rem;border: 1px solid #ddd;border-radius: 6px;}input[type="text"] {width: 100%;padding: 10px;border: 1px solid #ccc;border-radius: 6px;font-size: 1rem;margin-bottom: 1rem;}button {width: 100%;padding: 10px;border: none;border-radius: 6px;background: #007bff;color: white;font-size: 1rem;cursor: pointer;}button:hover {background: #0056b3;}.tip {margin-top: 0.8rem;font-size: 0.9rem;color: #666;}</style>
</head>
<body>
<div class="captcha-container"><h2>请输入验证码</h2><div class="error" id="errorMsg">验证码错误,请重新输入</div><form action="/zhcms/captcha/check" method="post"><img src="/zhcms/captcha.svl"onclick="this.src='/zhcms/captcha.svl?'+Math.random()"alt="验证码" title="点击图片更换验证码"/><input type="text" name="code" placeholder="请输入验证码" required/><button type="submit">提交</button><div class="tip">看不清?点击图片刷新验证码</div></form>
</div><script>// 从 URL 获取参数const params = new URLSearchParams(window.location.search);if (params.get("error") === "1") {document.getElementById("errorMsg").style.display = "block";}
</script>
</body>
</html>
页面效果如图所示:
6. 常见问题 Q&A
Q1: 为什么验证码页面用 JSP 会被拦截?
A: 因为 Filter
拦截了 .jhtml
之外的请求时,转发到 JSP 也会触发过滤逻辑,容易形成死循环。改成 .html
并使用 JavaScript 处理错误提示即可。
Q2: 验证码图片不显示?
A: 检查 captcha.svl
是否正确映射到 SimpleImageCaptchaServlet
。同时确认浏览器是否缓存了图片,可以通过 ?Math.random()
动态刷新。
Q3: 校验通过后没有跳转回原页面?
A: 确保在 Filter
里使用 session.setAttribute("targetUrl", uri)
保存目标地址,并在 CaptchaCheckServlet
里取出并重定向。
Q4: 如何扩展更多拦截目录?
A: 只需在 protectedPaths
数组里新增路径名即可,例如 "newpath1", "newpath2"
。
7. 总结
通过以上几个步骤,我们就实现了一个完整的验证码拦截机制:
- Filter 拦截:利用正则表达式精确拦截
/xxx/数字.jhtml
的请求; - 验证码校验:用户输入后由
CaptchaCheckServlet
校验,成功后跳转回原始页面; - 用户体验优化:验证码页面保持 HTML,支持错误提示,UI 简洁美观;
- 常见问题处理:避免死循环,解决验证码图片缓存问题。
这种实现方式简单清晰、可维护性强,非常适合需要在项目中批量拦截页面并加上验证码验证的场景。