news 2026/10/20 6:06:34

JavaWeb登录注册源码实战:JSP+Servlet+MySQL从部署到安全改造

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb登录注册源码实战:JSP+Servlet+MySQL从部署到安全改造

简介:这份资源是一套基于JavaWeb技术栈实现用户登录与注册功能的完整项目源码,面向正在学习JSP、Servlet与MySQL数据库开发的初学者及课程设计人群,帮助理解Web应用中身份验证与数据持久化的核心流程。压缩包共34个文件,约3.56MB,包含7个java源文件与7个class编译文件、3个jsp页面、2个xml配置、2个properties配置以及sql脚本、jar依赖、js脚本等,覆盖前端表单、后端请求处理与数据库建表脚本,目录结构清晰,便于对照学习。资源中演示了JSP构建登录注册界面、Servlet处理POST请求、JDBC连接MySQL并采用预编译语句防止SQL注入,同时涉及session保存登录状态、密码加密存储与错误提示等常见处理思路。目前已有3050人学习下载,适合作为JavaWeb入门练手或课程作业参考,帮助读者快速搭建可运行的用户认证模块并理解各层协作方式。

1. 从一份 JSP 登录注册源码说起:为什么它仍是 JavaWeb 入门的硬通货

很多同学第一次接触 JavaWeb,都是从登录注册开始的。但真正拿到一份能跑的源码,和自己在课堂上敲几行 Servlet 完全是两回事。这份基于 JavaWeb 实现的登录注册项目,用的是 JSP + Servlet + MySQL 这套经典组合,没有 Spring 全家桶,没有前后端分离,就是最朴素的请求转发和 JDBC 直连。它适合谁?适合刚学完 JSP 语法、想搞明白一个完整请求从浏览器到数据库再回到页面到底经历了什么的人。也适合需要快速搭一个后台管理入口、不想引入重型框架的开发者。项目本身不复杂,但麻雀虽小,该有的分层、校验、会话管理一个不少。下面我就按实际拆包的顺序,把这份源码从运行到改造讲透。

2. 项目结构与技术选型:为什么是 JSP + Servlet + MySQL

2.1 目录结构长什么样

拿到源码包,解压后通常是一个标准的 Eclipse/IDEA Web 项目结构。我先把关键目录列出来,你对照自己的包看一眼:

LoginRegister/ ├── src/ │ └── com/ │ └── demo/ │ ├── dao/ # 数据库访问层 │ │ ├── UserDao.java │ │ └── impl/ │ │ └── UserDaoImpl.java │ ├── entity/ # 实体类 │ │ └── User.java │ ├── servlet/ # 控制层 │ │ ├── LoginServlet.java │ │ └── RegisterServlet.java │ └── util/ # 工具类 │ └── DBUtil.java ├── WebContent/ (或 web/) │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符 │ │ └── lib/ # 第三方 jar │ ├── css/ │ │ └── style.css │ ├── login.jsp │ ├── register.jsp │ └── index.jsp └── ...

这个结构是典型的 MVC 雏形:JSP 负责展示,Servlet 负责接收请求和调度,DAO 负责和数据库打交道,entity 负责数据承载。没有 Service 层,业务逻辑直接写在 Servlet 里,对于登录注册这种简单场景够用了。如果你要二次开发,建议先把 Service 层补上,后面加业务才不会乱。

2.2 技术栈选型与依赖

项目依赖的 jar 包不多,核心就三个:MySQL 驱动、JSTL(如果页面用了标签库)、Servlet API(Tomcat 自带)。我一般会在WEB-INF/lib下放:

  • mysql-connector-java-8.x.jar(对应 MySQL 8,如果是 5.7 用 5.1.x)
  • jstl.jar+standard.jar(可选,看页面是否用<c:if>这类标签)

数据库建表语句通常长这样:

CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(50) NOT NULL, `email` varchar(100) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

注意username加了唯一索引,这是注册时判断重复的关键。密码字段是明文存储,这一点后面避坑章节会专门说。

2.3 请求流转链路

一个登录请求的完整路径是这样的:浏览器提交login.jsp表单 →web.xml或@WebServlet映射到LoginServlet→ Servlet 调用UserDao.findByUsername()→ DAO 通过DBUtil拿连接执行 SQL → 返回User对象 → Servlet 判断密码 → 成功则session.setAttribute("user", user)并重定向到index.jsp,失败则转发回login.jsp带错误信息。

这条链路虽然短,但每一环都有坑。比如中文乱码、连接未关闭、重定向和转发的区别,后面都会展开。

3. 核心模块拆解:从注册到登录的完整代码链路

3.1 数据库工具类 DBUtil

先看DBUtil,它是整个项目连接数据库的入口。常见写法是静态代码块加载驱动,然后提供一个getConnection()方法:

public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/login_db?useSSL=false&serverTimezone=UTC&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { // 依次关闭,注意顺序和 null 判断 } }

这里有几个参数要改:URL里的数据库名、USER、PASSWORD换成你自己的。serverTimezone=UTC是 MySQL 8 必须带的,否则会报时区错误。characterEncoding=utf8防止中文乱码。我一般还会把连接池加上,但这份源码是直连,跑通没问题,并发一高就顶不住。

3.2 实体类与 DAO 层

User实体类就是几个字段加 getter/setter,没什么好说的。重点看UserDaoImpl里的两个方法:

public class UserDaoImpl implements UserDao { @Override public User findByUsername(String username) { String sql = "SELECT * FROM user WHERE username = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setPassword(rs.getString("password")); user.setEmail(rs.getString("email")); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; } @Override public boolean insertUser(User user) { String sql = "INSERT INTO user(username, password, email) VALUES(?, ?, ?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, user.getUsername()); ps.setString(2, user.getPassword()); ps.setString(3, user.getEmail()); return ps.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); } return false; } }

这里用了 try-with-resources,连接和语句会自动关闭,比手动 finally 里 close 干净。PreparedStatement用?占位符,能防 SQL 注入,这是底线。注意findByUsername返回 null 表示用户不存在,注册时靠这个判断重名。

3.3 注册 Servlet 的校验逻辑

RegisterServlet的doPost方法通常这么写:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); String email = request.getParameter("email"); // 后端二次校验,不能只靠前端 if (username == null || username.trim().isEmpty() || password == null || password.length() < 6) { request.setAttribute("msg", "用户名不能为空,密码至少6位"); request.getRequestDispatcher("register.jsp").forward(request, response); return; } UserDao userDao = new UserDaoImpl(); if (userDao.findByUsername(username) != null) { request.setAttribute("msg", "用户名已存在"); request.getRequestDispatcher("register.jsp").forward(request, response); return; } User user = new User(); user.setUsername(username); user.setPassword(password); // 实际项目要加密 user.setEmail(email); if (userDao.insertUser(user)) { response.sendRedirect("login.jsp"); } else { request.setAttribute("msg", "注册失败,请重试"); request.getRequestDispatcher("register.jsp").forward(request, response); } }

关键点:setCharacterEncoding必须在getParameter之前调用,否则中文用户名会乱码。校验分两层,前端 JS 校验用户体验,后端校验保安全。注册成功用sendRedirect跳登录页,失败用forward回注册页并带错误信息,这样地址栏不会变,刷新不会重复提交。

3.4 登录 Servlet 与会话管理

登录逻辑和注册类似,核心是密码比对和 session 写入:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); UserDao userDao = new UserDaoImpl(); User user = userDao.findByUsername(username); if (user == null || !user.getPassword().equals(password)) { request.setAttribute("msg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); return; } HttpSession session = request.getSession(); session.setAttribute("user", user); response.sendRedirect("index.jsp"); }

request.getSession()默认会创建新会话,如果只想取已有会话可以用getSession(false)。登录成功后把整个user对象放进 session,后续页面用${sessionScope.user.username}就能取到。注意密码比对这里用的是equals,且没有做加密,这是教学项目的通病。

3.5 页面层的 JSP 写法

login.jsp的表单提交到LoginServlet,同时用 JSTL 显示错误信息:

<form action="LoginServlet" method="post"> 用户名:<input type="text" name="username" required><br> 密码:<input type="password" name="password" required><br> <input type="submit" value="登录"> </form> <c:if test="${not empty msg}"> <p style="color:red">${msg}</p> </c:if>

index.jsp里做登录拦截:

<c:if test="${empty sessionScope.user}"> <c:redirect url="login.jsp"/> </c:if> <p>欢迎,${sessionScope.user.username}</p>

这种拦截方式简单,但每个页面都要写一遍。更好的做法是加一个 Filter,后面改造章节会讲。

4. 跑起来:环境搭建与部署步骤

4.1 环境准备清单

跑这个项目需要:JDK 8 或 11、Tomcat 8.5 或 9、MySQL 5.7 或 8.0、IDE 用 Eclipse 或 IDEA 都行。我建议用 JDK 8 + Tomcat 8.5,兼容性最好,不容易出玄学问题。MySQL 版本要和驱动 jar 匹配,8.0 的库用 8.x 驱动,5.7 的库用 5.1.x 驱动,混用会报No suitable driver。

4.2 数据库初始化

先建库建表:

CREATE DATABASE login_db DEFAULT CHARSET utf8mb4; USE login_db; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(50) NOT NULL, email VARCHAR(100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

然后改DBUtil里的连接信息,确保和你的库一致。如果启动报Access denied,检查用户名密码;报Unknown database,检查库名;报时区错误,URL 里加serverTimezone=Asia/Shanghai。

4.3 项目导入与部署

IDEA 里选File → New → Project from Existing Sources,选源码目录,识别为 Web 项目。然后在Project Structure里配置 Artifacts,确保WEB-INF/lib下的 jar 被打进输出。Tomcat 配置里部署这个 Artifact,启动后访问http://localhost:8080/项目名/login.jsp。

Eclipse 的话,File → Import → Existing Projects into Workspace,然后右键项目Run As → Run on Server。如果报ClassNotFoundException,检查 jar 是否在WEB-INF/lib下而不是只加到了 Build Path。

4.4 验证流程

启动后先访问注册页,填一个用户名密码,提交后看是否跳转到登录页。再用刚注册的账号登录,成功应进入index.jsp并显示用户名。如果注册后数据库没数据,看控制台 SQL 异常;如果登录一直提示密码错误,检查数据库里存的是不是明文,以及页面传参有没有空格。

提示:Tomcat 控制台如果出现中文乱码,在server.xml的 Connector 里加URIEncoding="UTF-8",或者在 IDEA 的 VM options 里加-Dfile.encoding=UTF-8。

5. 避坑与常见问题排查

5.1 中文乱码:现象、原因、解决

现象:注册时输入中文用户名,数据库里显示???。原因:请求编码和数据库编码不一致。解决:doPost第一行加request.setCharacterEncoding("UTF-8");数据库连接 URL 加characterEncoding=utf8;建库时用utf8mb4。如果是 GET 请求传中文,Tomcat 8 以上默认 UTF-8,8 以下要改server.xml。

5.2 空指针异常:连接为 null

现象:启动后第一次访问就报NullPointerException,堆栈指向 DAO。原因:DBUtil.getConnection()返回 null,通常是驱动类没加载成功或 URL 写错。解决:检查Class.forName的驱动类名,MySQL 8 是com.mysql.cj.jdbc.Driver,5.x 是com.mysql.jdbc.Driver;检查 jar 是否真的在WEB-INF/lib下。

5.3 重复注册:唯一索引没生效

现象:同一个用户名能注册两次。原因:建表时username没加UNIQUE,或者代码里先查后插之间有并发窗口。解决:给username加唯一索引,插入时捕获SQLIntegrityConstraintViolationException并提示“用户名已存在”。先查后插只能防君子,防不了并发。

5.4 刷新重复提交:转发和重定向用混

现象:注册成功后按 F5,又插入一条重复数据。原因:成功后用了forward而不是sendRedirect。解决:增删改操作成功后一律用response.sendRedirect(),让浏览器发起新请求。查询操作用forward没问题。

5.5 会话失效:session 取不到值

现象:登录成功跳转后,index.jsp里取不到sessionScope.user。原因:可能用了response.sendRedirect但 session 没写进去,或者浏览器禁用了 Cookie。解决:确认session.setAttribute在重定向之前执行;如果禁用 Cookie,需要用 URL 重写response.encodeRedirectURL()。

6. 二次开发改造:从明文密码到过滤器拦截

这份源码能跑,但离能用还有距离。我拿到手第一件事就是改密码存储。明文存密码是血泪教训,一旦库被拖,所有用户密码直接暴露。改造思路是用MessageDigest做 MD5 加盐,或者直接上 BCrypt。注册时加密,登录时把输入的密码同样加密再比对:

public static String md5(String input) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }

注册时user.setPassword(md5(password + "固定盐")),登录时同样处理再比对。注意 MD5 已经不安全,生产环境用 BCrypt,这里只是演示改造位置。

第二个改造点是加登录过滤器,替代每个 JSP 里写<c:if>拦截:

@WebFilter(urlPatterns = {"/index.jsp", "/user/*"}) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(false); if (session == null || session.getAttribute("user") == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(req, resp); } }

这样需要登录才能访问的页面统一走过滤器,新增页面只要加进urlPatterns就行,不用改 JSP。

第三个改造点是引入连接池,把DBUtil里的DriverManager换成 Druid 或 HikariCP。直连每次请求都新建连接,并发一高数据库就顶不住。连接池配置好初始连接数和最大连接数,getConnection从池里取,用完close是归还而不是真关闭。

验证改造是否成功,我一般走三步:先注册一个新用户,查数据库看密码是不是密文;然后退出登录直接访问index.jsp,看是否被踢回登录页;最后用 JMeter 或 ab 压一下登录接口,看连接池有没有生效。从那以后我每次拿到这类教学源码,都强制先改密码存储和加过滤器,再谈其他功能。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 6:06:29

多智能体无人机编队控制:Voronoi与势场避障的Matlab仿真实践

提到无人机编队控制&#xff0c;很多人第一反应是“几架飞机排成队飞个造型”。但真正做过这个方向的人都知道&#xff0c;编队飞行最难的地方不在于“把队形摆整齐”&#xff0c;而在于一堆无人机在空间受限、障碍物穿插的环境里&#xff0c;如何既保持队形稳定&#xff0c;又…

作者头像 李华
网站建设 2026/10/20 6:05:43

AI智能体如何重构获客转化链路,降低企业获客成本

获客成本越抬越高&#xff0c;线索进来一大把&#xff0c;最后真金白银掏钱的却寥寥无几。这个问题我不止一次听身边的运营朋友抱怨过&#xff1a;投放停不下来&#xff0c;停了就没量&#xff1b;人工客服三班倒&#xff0c;还是漏掉不少深夜咨询&#xff1b;辛辛苦苦拉个群&a…

作者头像 李华
网站建设 2026/10/20 6:02:27

AI工具半年实测:分类经验与挑选原则,高效工作流如何落地

半年时间&#xff0c;我几乎把市面上能叫上名字的 AI 工具都试了一遍&#xff0c;从对话式模型、绘图生成、录音转写、视频脚本到各种效率插件&#xff0c;踩过的坑比写过的 Prompt 还多。最后留在工作台里的&#xff0c;不是功能最全的&#xff0c;也不是评分最高的&#xff0…

作者头像 李华
网站建设 2026/10/20 6:01:59

C++五子棋实战项目:禁手规则与Alpha-Beta剪枝实现

简介&#xff1a;这是一份面向C初学者与游戏开发入门者的五子棋实战项目源码&#xff0c;聚焦人机对战逻辑、AI决策实现与竞技规则落地。资源完整包含可直接编译运行的VS6工程&#xff08;.dsw/.dsp&#xff09;&#xff0c;含4个核心cpp文件&#xff08;main.cpp、AI.cpp、JUD…

作者头像 李华
网站建设 2026/10/20 6:01:51

人形机器人双足奔跑与口型跟随:从动态稳定到视觉运动闭环

早上在行业信息流里翻到两条热度最高的消息&#xff0c;一条是某款双足平台Bolt跑出了35km/h的速度&#xff0c;另一条是人形机器人对着镜子练嘴型&#xff0c;日报标题写得很俏皮——“机器人学会照镜子练嘴皮子了”。一条在猛冲猛跑&#xff0c;一条在对着镜子做表情&#xf…

作者头像 李华
网站建设 2026/10/20 6:01:22

2.5GBASE-T PHY驱动中的PBO功率回退机制:从原理到量产调优全解析

最近在调一块 2.5Gbps 网卡的 PHY 驱动时&#xff0c;碰上一个绕不开的话题&#xff1a;PBO&#xff08;Power Backoff&#xff09;功率回退机制。一开始我以为它只是个省电开关&#xff0c;真正把驱动代码打开之后才发现&#xff0c;这家伙直接关系到整板功耗、辐射特性和链路…

作者头像 李华