ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JavaWeb登录注册完整案例:JSP+Servlet+JavaBean+MySQL从零实现

JavaWeb登录注册完整案例:JSP+Servlet+JavaBean+MySQL从零实现 简介这份资源是一套基于JavaWeb技术栈实现用户登录与注册功能的完整项目源码面向正在学习JSP、Servlet与MySQL数据库开发的初学者及课程实践者帮助理解Web应用中用户认证模块的完整实现流程。压缩包共34个文件约3.56MB包含7个Java源文件与7个编译后的class文件、3个JSP页面、2个XML配置、2个properties配置以及SQL脚本、jar依赖和前端JS等覆盖从视图层到数据访问层的核心代码。项目围绕JSP构建登录注册表单界面通过Servlet处理请求并借助JDBC连接MySQL完成用户信息校验与存储同时涉及session状态保持、密码加密及参数化查询防注入等安全处理思路。目前已有3049人学习下载适合对照源码梳理项目结构、理解请求响应流程并在此基础上扩展错误提示与用户体验优化。1. 从零手搓一套 JSP JavaBean MySQL 登录注册这套源码到底能帮你省下多少时间如果你正在搜「javaweb项目完整案例mysql」大概率不是想听 Servlet 生命周期这种教科书内容而是想找一套能直接跑起来、能改、能交作业或者能当项目脚手架的东西。这套基于 JavaWeb 实现的登录及注册功能技术栈就是最经典的 JSP Servlet JavaBean MySQL没有 Spring 那一层封装也没有 Maven 多模块的复杂度打开 IDEA 配好 Tomcat 就能跑。它解决的核心问题很具体用户表设计、前端表单校验、后端参数接收、数据库增删改查、Session 状态保持、登录失败提示这一整条链路全部打通。适合谁刚学完 JSP 语法但不知道怎么串成完整项目的人需要交课程设计但不想从零搭环境的人以及想回头补一补原生 Servlet 请求流转细节的开发者。这套东西不高级但它把「一个请求从浏览器到数据库再回来」的每一步都摊开给你看了这种透明感在 Spring 全家桶里反而很难获得。2. 环境搭建与数据库建表别让第一步就翻车2.1 JDK、Tomcat、MySQL 的版本咬合关系这套项目对版本不算挑剔但有几个组合是经过验证比较稳的JDK 8 或 JDK 11Tomcat 8.5 或 9.0MySQL 5.7 或 8.0。我一般会推荐 JDK 8 Tomcat 8.5 MySQL 5.7 这个组合原因是网上大部分 JavaWeb 老教程都基于这个环境你遇到问题时搜到的答案匹配度最高。如果你用 JDK 17 配 Tomcat 10Servlet 的包名从javax.servlet变成了jakarta.servlet这套源码里的 import 全部要改新手很容易在这里卡住。IDEA 里配置 Tomcat 的步骤打开 Run/Debug Configurations点左上角加号选 Tomcat Server → Local在 Server 标签页里找到 Tomcat 安装目录在 Deployment 标签页点加号选 Artifact把项目的 war exploded 加进去。Application context 建议设成/login这样浏览器访问路径就是http://localhost:8080/login/。注意IDEA 社区版没有 Tomcat Server 选项这是很多人第一次就卡住的地方。社区版只能用 Smart Tomcat 插件或者手动部署到 Tomcat 的 webapps 目录。2.2 用户表设计字段不多但每个都有讲究数据库建表看起来简单但字段类型和约束如果一开始没设对后面写代码时会出现各种玄学问题。下面是我一般会用的建表语句CREATE DATABASE IF NOT EXISTS javaweb_login DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE javaweb_login; CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT COMMENT 主键自增, username VARCHAR(50) NOT NULL COMMENT 用户名登录账号, password VARCHAR(64) NOT NULL COMMENT 密码建议存MD5值, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱注册时可选填, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户信息表;这段 SQL 里有三个点值得展开说。第一字符集用utf8mb4而不是utf8因为 MySQL 的utf8其实是阉割版存不了 emoji 和部分生僻字注册时用户名里带特殊字符就会报错。第二username上加了唯一索引uk_username这是防止重复注册的第一道防线但代码里仍然要在插入前查一次因为靠数据库报错来提示用户「用户名已存在」体验很差。第三password字段长度给了 64因为如果你用 MD5 存密码32 位十六进制字符串刚好放得下如果后面想升级成 SHA-256 也不用改表结构。2.3 项目目录结构与依赖 jar 包原生 JavaWeb 项目没有 Maven 帮你管依赖所有 jar 包要手动放进WEB-INF/lib目录。这套登录注册功能需要的 jar 包不多核心就是 MySQL 驱动项目根目录/ ├── src/ │ └── com/ │ └── login/ │ ├── dao/ # 数据访问层 │ │ └── UserDao.java │ ├── entity/ # 实体类 │ │ └── User.java │ ├── servlet/ # 控制层 │ │ ├── LoginServlet.java │ │ └── RegisterServlet.java │ └── util/ # 工具类 │ └── DBUtil.java ├── web/ │ ├── WEB-INF/ │ │ ├── lib/ │ │ │ └── mysql-connector-java-8.0.28.jar │ │ └── web.xml │ ├── css/ │ │ └── style.css │ ├── login.jsp │ ├── register.jsp │ └── success.jspMySQL 驱动版本要和你装的 MySQL 服务端版本匹配。MySQL 5.7 用mysql-connector-java-5.1.xMySQL 8.0 用mysql-connector-java-8.0.x。用错了会在启动时报No suitable driver found或者连接时抛Communications link failure。把 jar 包放进WEB-INF/lib之后还要在 IDEA 的 Project Structure → Modules → Dependencies 里确认这个目录被加进了依赖否则编译时能找到类但运行时找不到。3. 从表单到数据库登录注册的完整请求链路拆解3.1 前端 JSP 页面表单提交的细节决定后端要不要擦屁股登录和注册页面看起来就是两个表单但表单的method、action和输入框的name属性如果写错后端收到的就是 null。先看登录页面的核心代码!-- login.jsp -- % page contentTypetext/html;charsetUTF-8 languagejava % html head title用户登录/title /head body h2登录/h2 !-- 表单提交到 LoginServlet用 POST 方法 -- form actionLoginServlet methodpost label用户名/label input typetext nameusername requiredbr label密 码/label input typepassword namepassword requiredbr input typesubmit value登录 a hrefregister.jsp没有账号去注册/a /form !-- 登录失败时显示错误信息 -- p stylecolor:red;${requestScope.msg}/p /body /html这里有几个容易忽略的点。actionLoginServlet用的是相对路径实际请求地址是/login/LoginServlet这个映射关系要在web.xml或者WebServlet注解里配好。methodpost是必须的用 get 的话密码会出现在 URL 里既不安全也不符合基本规范。两个 input 的name属性值username和password必须和 Servlet 里request.getParameter()的参数名完全一致大小写都不能差。required是 HTML5 自带的非空校验能挡住一部分空提交但不能替代后端校验因为用户可以禁用 JavaScript 或者直接构造请求绕过。注册页面比登录多一个确认密码和邮箱字段结构类似但多了一个前端校验的逻辑!-- register.jsp 核心部分 -- form actionRegisterServlet methodpost onsubmitreturn checkForm() label用户名/label input typetext nameusername idusername requiredbr label密 码/label input typepassword namepassword idpassword requiredbr label确认密码/label input typepassword nameconfirmPassword idconfirmPassword requiredbr label邮 箱/label input typeemail nameemailbr input typesubmit value注册 /form script function checkForm() { var pwd document.getElementById(password).value; var confirmPwd document.getElementById(confirmPassword).value; if (pwd ! confirmPwd) { alert(两次输入的密码不一致); return false; // 阻止表单提交 } if (pwd.length 6) { alert(密码长度不能少于6位); return false; } return true; } /scriptonsubmitreturn checkForm()这个写法里return false会阻止表单提交return true才放行。如果只写checkForm()不写return那么无论函数返回什么表单都会提交这是很多人踩过的坑。前端校验的目的是提升用户体验减少无效请求但后端必须再校验一遍因为前端代码在浏览器里用户想改就能改。3.2 后端 Servlet 处理参数接收、业务判断、页面跳转登录 Servlet 是整个链路的核心它要做四件事接收参数、调用 DAO 查数据库、判断结果、决定跳转。先看代码// LoginServlet.java WebServlet(/LoginServlet) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置请求和响应的编码防止中文乱码 request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); // 1. 接收参数 String username request.getParameter(username); String password request.getParameter(password); // 2. 后端非空校验前端校验可被绕过 if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { request.setAttribute(msg, 用户名和密码不能为空); request.getRequestDispatcher(login.jsp).forward(request, response); return; } // 3. 调用 DAO 查询用户 User user userDao.findByUsernameAndPassword(username, password); // 4. 判断结果并跳转 if (user ! null) { // 登录成功把用户信息存入 Session request.getSession().setAttribute(currentUser, user); response.sendRedirect(success.jsp); } else { // 登录失败转发回登录页并带错误提示 request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } } }这段代码里有几个关键决策点。request.setCharacterEncoding(UTF-8)必须放在所有getParameter()之前否则不生效这是 Servlet 规范决定的。response.setContentType(text/html;charsetUTF-8)是告诉浏览器用什么编码解析返回的 HTML。登录成功用sendRedirect而不是forward是因为重定向会改变浏览器地址栏用户刷新页面不会重复提交表单登录失败用forward是因为需要把错误信息通过request域带到 JSP 页面上显示。request.getSession()会自动创建 Session 对象把 User 对象存进去之后其他页面可以通过session.getAttribute(currentUser)判断用户是否已登录。注册 Servlet 的逻辑比登录多一步「查重」// RegisterServlet.java 核心逻辑 WebServlet(/RegisterServlet) public class RegisterServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); String username request.getParameter(username); String password request.getParameter(password); String email request.getParameter(email); // 先查用户名是否已存在 if (userDao.findByUsername(username) ! null) { request.setAttribute(msg, 用户名已被注册); request.getRequestDispatcher(register.jsp).forward(request, response); return; } // 构建 User 对象并插入数据库 User user new User(); user.setUsername(username); user.setPassword(password); // 实际项目应存 MD5 值 user.setEmail(email); boolean success userDao.insert(user); if (success) { request.setAttribute(msg, 注册成功请登录); request.getRequestDispatcher(login.jsp).forward(request, response); } else { request.setAttribute(msg, 注册失败请稍后重试); request.getRequestDispatcher(register.jsp).forward(request, response); } } }注册成功后转发到登录页而不是直接跳转到 success 页是因为注册只是创建了账号还没有建立登录态。如果注册后直接进主页Session 里没有用户信息后续操作会出问题。3.3 DAO 层与数据库连接JDBC 工具类的封装与资源释放DAO 层负责和数据库打交道这套项目里我一般会把 JDBC 连接封装成一个工具类避免在每个方法里重复写驱动加载和连接获取// DBUtil.java public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/javaweb_login ?useUnicodetruecharacterEncodingutf8useSSLfalse serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 你的数据库密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 释放资源顺序不能错ResultSet → Statement → Connection public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }连接 URL 里的参数每一个都有作用。useUnicodetruecharacterEncodingutf8保证中文不乱码。useSSLfalse关掉 SSL 警告本地开发用不上。serverTimezoneAsia/Shanghai是 MySQL 8.0 必须加的不加会报时区错误。驱动类名com.mysql.cj.jdbc.Driver是 MySQL 8.0 的写法5.1 版本用com.mysql.jdbc.Driver。UserDao 里两个核心方法// UserDao.java public class UserDao { // 登录查询根据用户名和密码查用户 public User findByUsernameAndPassword(String username, String password) { String sql SELECT * FROM user WHERE username ? AND password ?; Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); rs ps.executeQuery(); if (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setEmail(rs.getString(email)); return user; } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, rs); } return null; } // 注册插入新增一条用户记录 public boolean insert(User user) { String sql INSERT INTO user(username, password, email) VALUES(?, ?, ?); Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); 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; } finally { DBUtil.close(conn, ps, null); } } }用PreparedStatement而不是Statement拼接字符串是为了防止 SQL 注入。如果写成SELECT * FROM user WHERE username username 用户在用户名输入框里填 OR 11就能绕过密码验证直接登录。PreparedStatement的?占位符会把参数当作纯字符串处理不会解析成 SQL 语法。4. 避坑与排查这套登录注册跑不起来时先看这几个地方4.1 中文乱码POST 和 GET 的处理方式不一样现象注册时输入中文用户名存进数据库变成???或者登录时提示「用户名或密码错误」但数据库里明明有这个用户。原因POST 请求的乱码可以通过request.setCharacterEncoding(UTF-8)解决但 GET 请求的参数在 URL 里Tomcat 8.5 之前默认用 ISO-8859-1 解码setCharacterEncoding对 GET 无效。解决Tomcat 8.5 及以上版本在conf/server.xml的 Connector 标签里加了URIEncodingUTF-8默认配置GET 乱码基本不会出现。如果用的是更老的 Tomcat要么改 server.xml要么在代码里手动转码new String(username.getBytes(ISO-8859-1), UTF-8)。数据库连接 URL 里的characterEncodingutf8也要确认加上。4.2 ClassNotFoundException驱动 jar 包放了但没生效现象启动 Tomcat 后访问登录页面提交表单时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因jar 包虽然放进了WEB-INF/lib但 IDEA 的项目结构里这个目录没有被识别为依赖库编译时能找到类因为 IDE 索引了运行时 Tomcat 的类加载器找不到。解决打开 Project Structure → Modules → Dependencies点加号选 JARs or directories把WEB-INF/lib目录加进去Scope 选 Provided 或 Compile。另外确认 Artifact 里WEB-INF/lib下的 jar 包被包含进了输出布局否则部署到 Tomcat 的 war 包里没有这些 jar。4.3 表单提交后 404Servlet 映射路径对不上现象点击登录按钮后浏览器显示 404地址栏是http://localhost:8080/login/LoginServlet。原因WebServlet(/LoginServlet)注解里的路径和表单action里的路径不一致或者web.xml里配了url-pattern但注解和 xml 同时存在导致冲突。解决确认WebServlet的值和 form 的 action 完全一致包括大小写。如果项目里同时有web.xml和注解检查web.xml的metadata-complete属性如果是true注解会被忽略只认 xml 配置。建议二选一不要混用。4.4 登录成功但刷新页面又回到登录页Session 没设对现象登录后跳转到 success.jsp 正常显示但按 F5 刷新后跳回了登录页。原因success.jsp 里判断登录态的代码写错了比如用了request.getAttribute(currentUser)而不是session.getAttribute(currentUser)request 域在一次请求结束后就失效了。解决在 success.jsp 开头加判断% Object currentUser session.getAttribute(currentUser); if (currentUser null) { response.sendRedirect(login.jsp); return; } %同时确认 LoginServlet 里存的是request.getSession().setAttribute(currentUser, user)key 的名字两边要一致。4.5 MySQL 8.0 时区报错The server time zone value is unrecognized现象连接数据库时抛异常java.sql.SQLException: The server time zone value ?D1ú±ê×?ê±?? is unrecognized。原因MySQL 8.0 的驱动要求明确指定时区不指定就会用服务器默认时区中文 Windows 系统下会返回乱码的时区名。解决在 JDBC URL 里加serverTimezoneAsia/Shanghai或者改成serverTimezoneGMT%2B8。如果用的是 MySQL 5.7 驱动连 8.0 服务端也建议加上这个参数。5. 密码加密与登录态校验从能跑到能用的最后一步5.1 用 MD5 给密码加一层别让数据库裸奔前面 DAO 层代码里密码是明文存的这在教学项目里常见但如果你要把这套代码用到任何真实场景必须改。最简单的做法是注册时对密码做 MD5 摘要登录时对输入的密码做同样处理再比对// MD5Util.java public class MD5Util { 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(MD5算法不可用, e); } } }注册时把user.setPassword(password)改成user.setPassword(MD5Util.md5(password))登录时把userDao.findByUsernameAndPassword(username, password)改成userDao.findByUsernameAndPassword(username, MD5Util.md5(password))。这样数据库里存的就是 32 位十六进制字符串即使数据库被拖库攻击者也不能直接拿到明文密码。注意MD5 已经不是安全的密码哈希算法了彩虹表可以反查常见密码。真实项目应该用 BCrypt 或 Argon2但在这个教学项目里MD5 的目的是让你理解「密码不能明文存」这件事而不是提供一个生产级方案。5.2 登录拦截别让未登录用户直接访问 success.jsp现在 success.jsp 虽然加了 Session 判断但每个需要登录才能访问的页面都要写一遍太麻烦。更常见的做法是用一个 Filter 统一拦截// LoginFilter.java WebFilter(/*) public class LoginFilter implements Filter { // 不需要登录就能访问的路径 private static final String[] WHITE_LIST {/login.jsp, /register.jsp, /LoginServlet, /RegisterServlet, /css/}; Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String path request.getRequestURI().substring(request.getContextPath().length()); // 白名单路径直接放行 for (String white : WHITE_LIST) { if (path.startsWith(white)) { chain.doFilter(req, resp); return; } } // 非白名单路径检查 Session Object user request.getSession().getAttribute(currentUser); if (user ! null) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /login.jsp); } } }WebFilter(/*)表示拦截所有请求白名单里的路径直接放行其余路径检查 Session 里有没有currentUser。这个 Filter 的坑在于静态资源也会被拦截所以 css、js、图片目录都要加进白名单否则登录页面样式会丢失。5.3 验证这套代码是否真的跑通了部署到 Tomcat 之后按这个顺序走一遍访问http://localhost:8080/login/register.jsp填一个没注册过的用户名和密码提交后应该跳回登录页并提示「注册成功请登录」。打开数据库执行SELECT * FROM user确认多了一条记录且密码字段是 MD5 值。回到登录页用刚才的账号登录应该跳转到 success.jsp 并显示用户名。直接访问http://localhost:8080/login/success.jsp如果 Filter 配好了应该被重定向回登录页。最后试一下用错误的密码登录确认提示「用户名或密码错误」而不是 500 错误页。从那以后我每次拿到一套 JavaWeb 老项目都会先确认三件事JDBC URL 里有没有serverTimezoneWEB-INF/lib有没有被加进 Artifact 输出Filter 白名单有没有漏掉静态资源目录。这三步走完百分之八十的「跑不起来」问题都能定位到。希望帮到你。本文还有配套的精品资源点击获取
返回列表