如何在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 { @Override public 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/javaee http://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; @Override public 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 配置"); } } @Override protected 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 简洁美观;
- 常见问题处理:避免死循环,解决验证码图片缓存问题。
这种实现方式简单清晰、可维护性强,非常适合需要在项目中批量拦截页面并加上验证码验证的场景。
到此这篇关于如何在Java Web项目中优雅地实现验证码拦截与校验的文章就介绍到这了,更多相关Java Web验证码拦截与校验内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!