2026/5/24 0:08:36
网站建设
项目流程
国网交流建设公司网站,dw做网站怎么加视频,做化妆品注册和注册的网站有哪些,福州城市建设规划网站JSP JavaScript 实现验证码登录功能
在开发一个 Web 应用时#xff0c;用户登录几乎是每个系统都绕不开的环节。而为了防止恶意程序暴力破解密码#xff0c;加入图形验证码成了最基础、也最有效的防护手段之一。最近我在做 Java Web 练手项目时#xff0c;就动手实现了一套…JSP JavaScript 实现验证码登录功能在开发一个 Web 应用时用户登录几乎是每个系统都绕不开的环节。而为了防止恶意程序暴力破解密码加入图形验证码成了最基础、也最有效的防护手段之一。最近我在做 Java Web 练手项目时就动手实现了一套基于JSP Servlet JavaScript的动态验证码登录系统。整个过程不仅加深了我对前后端协作的理解也让我不再“畏惧”图像生成这类看似复杂的操作。今天我就把这套方案完整地梳理一遍——从后端如何用 Java 绘图 API 生成带干扰线的验证码图片到前端如何通过点击刷新提升体验再到登录时如何安全比对一气呵成。核心机制一次请求一张图一个 Session这个系统的精妙之处在于“轻量但完整”浏览器请求一张图片 → 后端动态绘制并输出 → 把真实值存进当前用户的 Session → 前端提交时取出比对。整个流程不依赖数据库、也不需要额外存储服务仅靠 HTTP 协议本身的会话机制就能完成验证闭环。而 JavaScript 的加入则让用户体验更流畅不用刷新页面点一下验证码就能换新图。验证码图像怎么来的Java 也能“画画”很多人以为生成图片是件很“重”的事其实不然。Java 提供了java.awt和javax.imageio这类原生 API完全可以像画板一样在内存中绘图。我们先封装一个核心工具类ValidateCode.java。它负责创建 BufferedImage 对象并在其上绘制背景、干扰线和随机字符。package org.lanqiao.util; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.OutputStream; import java.util.Random; public class ValidateCode { private int width 120; private int height 40; private int codeCount 4; private int lineCount 50; private String code; private BufferedImage buffImg; private char[] codeSequence { A, B, C, D, E, F, G, H, I, J, K, L, M, N, P, Q, R, S, T, U, V, W, X, Y, Z, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; public ValidateCode() { this.createCode(); } public ValidateCode(int width, int height) { this.width width; this.height height; this.createCode(); } public ValidateCode(int width, int height, int codeCount, int lineCount) { this.width width; this.height height; this.codeCount codeCount; this.lineCount lineCount; this.createCode(); } private void createCode() { int x width / (codeCount 2); int fontHeight height - 2; int codeY height - 4; Random random new Random(); buffImg new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g buffImg.createGraphics(); // 白色背景 g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); // 抗锯齿 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 干扰线 for (int i 0; i lineCount; i) { int xs random.nextInt(width); int ys random.nextInt(height); int xe xs random.nextInt(width / 8); int ye ys random.nextInt(height / 8); int red random.nextInt(255); int green random.nextInt(255); int blue random.nextInt(255); g.setColor(new Color(red, green, blue)); g.drawLine(xs, ys, xe, ye); } // 生成并绘制验证码字符 StringBuilder randomCode new StringBuilder(); for (int i 0; i codeCount; i) { String strRand String.valueOf(codeSequence[random.nextInt(codeSequence.length)]); int red random.nextInt(255); int green random.nextInt(255); int blue random.nextInt(255); g.setColor(new Color(red, green, blue)); g.drawString(strRand, (i 1) * x, codeY); randomCode.append(strRand); } code randomCode.toString(); } public void write(OutputStream output) throws Exception { ImageIO.write(buffImg, png, output); } public BufferedImage getBuffImg() { return buffImg; } public String getCode() { return code; } }可以看到这段代码并没有什么高深的技术就是一步步“画”出来先铺底色再画杂乱的彩色线条作为干扰最后一个个写出扭曲感十足的字符。如果你希望更难识别还可以加上旋转、波浪变形等效果不过对于一般场景来说这样已经足够防住大多数自动化脚本了。字体也能内嵌避免部署依赖的小技巧为了让验证码看起来更有设计感而不是默认的宋体或 Arial我们可以加载自定义字体比如一种手写风格的.ttf文件。但直接引用外部文件会导致部署麻烦——万一服务器没装这个字体呢解决方案是把字体文件转成字节数组硬编码进 Java 类里。下面这个ImgFontByte.java就干了这件事package org.lanqiao.util; import java.awt.Font; import java.io.ByteArrayInputStream; public class ImgFontByte { public Font getFont(int fontHeight) { try { Font baseFont Font.createFont(Font.TRUETYPE_FONT, new ByteArrayInputStream(hex2byte(getFontByteStr()))); return baseFont.deriveFont(Font.PLAIN, fontHeight); } catch (Exception e) { return new Font(Arial, Font.PLAIN, fontHeight); } } private byte[] hex2byte(String str) { if (str null || str.length() % 2 ! 0) return null; byte[] b new byte[str.length() / 2]; for (int i 0; i str.length(); i 2) b[i / 2] (byte) Integer.decode(0x str.substring(i, i 2)).intValue(); return b; } private String getFontByteStr() { return 0001000000100040000400c04f532f327d8175d4000087740000005650434c5461e3d9fb000087cc...; } }虽然这一长串十六进制字符串看着吓人但它其实就是某个.ttf文件的二进制内容转换而来。只要你的 IDE 能编译过去字体就能正常加载。当然如果只是练习用途也可以直接去掉这部分逻辑使用系统默认字体即可。图片接口怎么暴露用 Servlet 接管请求前端要显示验证码图片自然要用img src...。那么这个src指向哪里答案是一个专门处理图像输出的 Servlet。我们定义一个ValidateCodeServlet.java映射路径为/vCodepackage org.lanqiao.util; 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(/vCode) public class ValidateCodeServlet extends HttpServlet { private static final long serialVersionUID 1L; Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType(image/jpeg); response.setHeader(Pragma, no-cache); response.setHeader(Cache-Control, no-cache); response.setDateHeader(Expires, 0); HttpSession session request.getSession(); ValidateCode vCode new ValidateCode(120, 40, 4, 50); session.setAttribute(code, vCode.getCode().toLowerCase()); vCode.write(response.getOutputStream()); } }关键点有三个1. 设置响应类型为image/jpeg告诉浏览器这是张图2. 禁用缓存确保每次请求都能拿到新图3. 把生成的真实验证码小写化后存入 Session后续登录验证时取出来比对。这样一来只要前端访问/vCode就会得到一张全新的验证码图片同时服务端也记住了它的内容。前端交互怎么做一行 JS 实现“点击换图”现在后端准备好了接下来是前端。我们在login.jsp中嵌入图片并绑定点击事件。% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% % String basePath request.getScheme() :// request.getServerName() : request.getServerPort() request.getContextPath() /; % !DOCTYPE html html head base href%basePath% meta http-equivContent-Type contenttext/html; charsetUTF-8 title用户登录/title script typetext/javascript window.onload function () { var img document.getElementById(vCodeImg); img.onclick function () { this.src vCode?date new Date(); }; }; /script style body { font-family: Arial, sans-serif; padding: 50px; text-align: center; } .form-group { margin: 15px 0; } input[typetext], input[typepassword] { padding: 8px; width: 200px; border: 1px solid #ccc; border-radius: 4px; } input[typesubmit] { padding: 10px 20px; background-color: #007bff; color: white; border: none; cursor: pointer; } input[typesubmit]:hover { background-color: #0056b3; } /style /head body h2用户登录系统/h2 form actionUserServlet/userVerify methodpost div classform-group 用户名input typetext namename requiredbr /div div classform-group 密码input typepassword namepwd requiredbr /div div classform-group 是否保存密码input typecheckbox namesave valuetruebr /div div classform-group 验证码input typetext nameverifyCode requiredbr img srcvCode idvCodeImg alt验证码 stylecursor:pointer; title点击换一张 /div div classform-group input typesubmit value登录 /div /form /body /html注意这里的 JavaScript 逻辑每次点击图片就把src修改为vCode?date时间戳。由于 URL 变了浏览器就不会使用缓存从而触发新的请求返回新验证码。这种“加参数防缓存”的做法非常常见尤其适用于动态资源。登录怎么验证Session 比对是关键最后一步当用户填写完信息点击登录时我们需要在后端进行校验。package org.lanqiao.web; 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 java.io.PrintWriter; WebServlet(/UserServlet/*) public class UserServlet extends HttpServlet { private static final long serialVersionUID 1L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetutf-8); PrintWriter out response.getWriter(); HttpSession session request.getSession(); String username request.getParameter(name); String password request.getParameter(pwd); String inputCode request.getParameter(verifyCode); String realCode (String) session.getAttribute(code); if (realCode null || !realCode.equalsIgnoreCase(inputCode)) { out.println(h3 stylecolor:red;验证码错误请重试/h3); out.println(a hreflogin.jsp返回登录页/a); return; } // 模拟用户名密码验证 if (admin.equals(username) 123456.equals(password)) { out.println(h3 stylecolor:green;登录成功欢迎回来 username /h3); } else { out.println(h3 stylecolor:red;用户名或密码错误/h3); } } }这里特别要注意的是验证码只能用一次。一旦比对失败或成功最好立即将 Session 中的code清除或更新防止被重复利用。否则攻击者可能截获一次有效验证码后反复尝试。实际运行与调试建议部署这套系统时记得检查以下几点所有 Java 类是否放入正确的包路径是否使用支持注解的 Tomcat 版本如 7若否需手动配置web.xml访问login.jsp时打开浏览器开发者工具查看/vCode请求是否返回 200 状态码且 Content-Type 是 image输入错误验证码时是否提示正确输入正确后能否进入下一步一个小坑有时候你会发现验证码明明输对了却提示错误——很可能是大小写问题。所以建议统一转为小写比较或者直接限制输入框只能输入大写。可以怎么改进几个实用方向功能改进建议验证码过期设置 Session 超时时间如 5 分钟或引入 Redis 存储并设置 TTL区分大小写前端用input.toUpperCase()强制转换后端同理处理中文验证码替换codeSequence为汉字数组并确保字体支持中文编码移动端适配使用 Base64 编码将图片嵌入 HTML减少请求数更强安全性加入滑动拼图、算术题等交互式验证方式写在最后基础功永远值得打磨如今 AI 已经能生成逼真的图像、语音甚至视频各种“无感验证”技术也在兴起。但作为一名开发者我始终认为理解底层原理掌握基本功才是应对变化的根本。就像这个小小的验证码系统它涉及了 Servlet 生命周期、图像处理、Session 管理、前后端交互等多个知识点。看似简单实则麻雀虽小五脏俱全。无论是毕业设计还是企业项目这类模块都是极佳的练手机会。下次当你看到登录页上的那张小图时不妨多看一眼——说不定它正悄悄守护着整个系统的安全入口。