
简介一套基于 JSP 与 SQL Server 的登录注册示例项目面向刚开始接触 Web 开发的学生与入门开发者围绕用户注册和登录两个核心场景演示从数据库设计、JDBC 连接、表单提交处理到 session 会话管理的完整做法。项目涉及密码安全、输入校验与常见异常处理适合作为课程设计、实验练习或小型系统登录模块的参考。压缩包共 46 个文件大小约 581KB其中以 28 个 JSP 页面为主同时包含 XML 配置、Eclipse/MyEclipse 工程描述文件、Java 源码与编译类文件等目录层次覆盖页面展示、业务逻辑与数据库交互便于对照学习。资源中保留了 IDE 工程配置便于导入后直接查看完整实现。已有 835 人学习下载。通过实际运行项目可以快速梳理用户认证 demo 的开发流程理解 JSP 如何通过 JDBC 操作 SQL Server并掌握基本环境配置、数据库连接参数调整与常见问题排查思路。1. 一套能用起来、也能抄作业的 JSP SQL Server 登录注册如果你打开过网上下载的 JSP 项目大概率见过这样一个压缩包里面躺着 .project、.classpath、src、WebContent还有一个叫“登录注册”的模块。这套 JSP SQL Server 登录注册源码就是一个典型的“能跑通、可抄改”的小项目——它把用户注册、登录校验、Session 会话管理、数据库读写串成一条完整链路适合那些刚学完 JSP 语法、jnative 却不知道怎么接数据库的人也适合课程设计需要一套“能演示、能答辩”的 Java Web 项目的学生。比起看到一半就放弃的各种零散博客这套 zip 把从建表到会话管理的过程都摆在了你面前下面我按拆包、搭环境、实现注册、实现登录、踩坑排查、再往工程化方向改进的顺序把整套源码讲透。2. 拆开压缩包项目结构、数据库设计与 JDBC 连接2.1 读懂 MyEclipse 项目目录哪些要部署哪些是配置文件先看压缩包里出现的这些路径.project、.classpath、.mymetadata、.myeclipse、.settings、src、WebContent、WebRoot。这是一份典型的 MyEclipse 导出项目。很多人拿到手第一反应是“怎么这么多乱文件夹”其实只需分清三层.project、.classpath、.settings是 Eclipse/MyEclipse 的工程配置文件IDE 靠它们识别项目类型和源码路径部署到 Tomcat 时不需要带过去。src是 Java 源码根目录存放 Servlet、Filter、工具类等 Java 文件。WebContent有的项目叫WebRoot是 Web 根目录JSP 页面和WEB-INF/web.xml都在这里启动 Tomcat 时实际上是把 WebContent 映射到应用上下文路径下运行。把压缩包解压后我一般先在 MyEclipse或 Eclipse EE 版里导入选择“Import → Existing Projects into Workspace”然后指定解压后的目录。导入后右键项目选“Properties → Targeted Runtimes”勾选你本机已配置的 Tomcat不然项目会报缺运行环境。提示项目里若同时出现 WebContent 和 WebRoot可能是不同版本工具的默认目录建议统一以 WebContent 为准把另一个目录从构建路径中排除。2.2 SQL Server 用户表设计T-SQL 建表要点与密码存储登录注册功能的核心是用户表。这套源码一般会在 SQL Server 里创建类似users的表字段包括用户 ID、用户名、密码等。密码这一列源码里常见的是存明文或简单的 MD5 摘要。我们自己复现时建议直接按下面这个表结构建CREATE TABLE [dbo].[users]( [id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY, [username] [nvarchar](50) NOT NULL UNIQUE, [password] [varchar](64) NOT NULL, [email] [nvarchar](100) NULL, [create_time] [datetime] NOT NULL DEFAULT GETDATE() )这段 T-SQL 建表语句里有几个地方值得解释IDENTITY(1,1)让id自增避免自己在 Java 代码里生成主键username加了UNIQUE约束在数据库层面禁止重名这是防止并发注册时重复用户名最有效的兜底password用varchar(64)是为了容纳 MD5 的 32 位十六进制字符串如果你在源码里看到的是varchar(50)那大概率是在存明文。create_time用DEFAULT GETDATE()自动填充注册时间省去 Java 端的时间维护。2.3 JDBC 连接 SQL Server驱动下载、URL 参数与三个经典坑JSP 里操作数据库绕不开 JDBC。连接 SQL Server 有两种常见驱动类型一种是微软官方mssql-jdbc系列 jar 包另一种是老项目里常见的 JTDS 驱动。源码里如果用com.microsoft.sqlserver.jdbc.SQLServerDriver那对应的是官方驱动。连接代码核心逻辑如下Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver); String url jdbc:sqlserver://localhost:1433;DatabaseNamelogin_demo;encryptfalse; String user sa; String password your_password; Connection conn DriverManager.getConnection(url, user, password);这里三个参数容易被忽略。localhost:1433是 SQL Server 默认端口但很多开发机的 SQL Server 用实例名安装端口可能不是 1433甚至 TCP/IP 协议默认都没启用。encryptfalse是 SQL Server 2017 及以上版本才需要的参数——新版驱动默认要求加密连接本地开发如果没配置证书会直接报错。user与password是数据库登录账号优先级高于 Windows 身份验证。注意别把驱动 jar 包放在 Tomcat 的 lib 目录下除非你确定不会换 Tomcat。否则端口被占用、项目迁移时最容易翻车的就是“本地能连换台机器就 ClassNotFoundException”。3. 注册功能落地从表单提交到数据库插入的完整链路3.1 注册页面的表单设计JSP 接收参数的两种方式注册页面本质是一个 HTML 表单提交方式必须是 POST否则用户名密码会出现在浏览器地址栏里。下面这段是典型的 JSP 注册表单form actionregister methodpost label用户名/label input typetext nameusername / label密码/label input typepassword namepassword / label邮箱/label input typetext nameemail / button typesubmit注册/button /formJSP 页面里接收参数新手最容易看到两种写法一种是% request.getParameter(username) %另一种是${param.username}。前者是 Scriptlet 写法后者是 EL 表达式写法更简洁。这套源码在提交处理上一般走的是 Servlet 路径表单action指向一个WebServlet(/register)注解的注册 Servlet然后在doPost()方法里用request.getParameter()取值。3.2 注册业务实现先查重、再加密、最后插入注册逻辑不是一个 JDBC 插入就完事正确顺序是先查用户名是否存在再写入数据库。下面这段 Java 代码模拟了源码里的核心处理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); try (Connection conn DBUtil.getConnection()) { String checkSql SELECT id FROM users WHERE username ?; PreparedStatement checkPs conn.prepareStatement(checkSql); checkPs.setString(1, username); ResultSet rs checkPs.executeQuery(); if (rs.next()) { request.setAttribute(msg, 用户名已存在); request.getRequestDispatcher(/register.jsp).forward(request, response); return; } String md5 MD5Util.md5(password); String insertSql INSERT INTO users (username, password, email) VALUES (?, ?, ?); PreparedStatement insertPs conn.prepareStatement(insertSql); insertPs.setString(1, username); insertPs.setString(2, md5); insertPs.setString(3, email); insertPs.executeUpdate(); response.sendRedirect(login.jsp?registeredtrue); } catch (Exception e) { e.printStackTrace(); request.setAttribute(msg, 系统异常请稍后再试); request.getRequestDispatcher(/register.jsp).forward(request, response); } }这段代码里有三个点需要细看。request.setCharacterEncoding(UTF-8)必须放在任何getParameter()之前否则中文用户名会出现乱码。PreparedStatement用?占位符替代字符串拼接这是防 SQL 注入的根本手段。sendRedirect和forward的区别是重定向是浏览器再发一次新请求地址栏变化转发是服务端内部跳转地址栏不变。注册成功用重定向可以避免用户刷新页面时重复提交表单。3.3 注册成功后的页面反馈避免“响应已提交”的隐藏坑如果你在源码里看到注册成功后使用request.setAttribute()再forward注意不要在forward之前调用response.getWriter().write()否则会抛IllegalStateException: 响应已提交。我把注册成功跳转改为sendRedirect的原因就在这重定向发生在响应体输出前是安全的而且天然解决了刷新页面重复插入数据的问题。学习这套源码时把这些细节改一改你会明显感觉代码质量提升了一个档次。提示源码里如果直接对password字段明文入库建议至少改成 MD5。虽然 MD5 已不算强加密但比明文裸奔好得多也足够应付课程设计演示。4. 登录功能与会话管理查询用户、校验密码、保持状态4.1 登录校验的 SQL 写法与防注入处理登录接口的逻辑比注册简单但细节更多。常见错误写法是拼接 SQL 字符串去查“有没有匹配的用户名和密码”比如SELECT * FROM users WHERE username张三 AND password123456。这种写法在 SQL Server 里有两个问题一是如果password存的是加密后的值你查询时也要先加密再比较二是字符串拼接极易被 SQL 注入。正确做法是使用PreparedStatement把用户名和密码作为参数传入String loginSql SELECT id, username FROM users WHERE username ? AND password ?; PreparedStatement loginPs conn.prepareStatement(loginSql); loginPs.setString(1, username); loginPs.setString(2, MD5Util.md5(password)); ResultSet rs loginPs.executeQuery(); if (rs.next()) { // 登录成功 }MD5Util.md5(password)这一步很关键注册时存的是摘要登录时也必须先算摘要再比对而不是拿明文去数据库里查。如果登录一直失败请先确认是不是加密这一步忘写了。username列存在UNIQUE约束所以按用户名查询最多返回一条记录实际中不建议直接把密码放进查询条件而是先查用户、再在 Java 层比对密码这样以后升级加密算法比如换成 BCrypt时不用改 SQL。4.2 Session 的保存与销毁登录状态如何维持用户验证通过后需要把用户信息放进HttpSession后续每个请求才能识别“这个用户是谁”。核心代码只有一句HttpSession session request.getSession(); session.setAttribute(user, username); session.setMaxInactiveInterval(1800);getSession()方法如果当前没有 session 会创建一个新的setAttribute(user, username)把用户名存进 sessionsetMaxInactiveInterval(1800)设置 30 分钟无操作后 session 自动失效。这是登录系统的关键行为服务器在内存中保存用户状态浏览器凭JSESSIONID这个 Cookie 在后续请求中取回自己的 session 对象。退出登录的写法同样值得说明HttpSession session request.getSession(false); if (session ! null) { session.removeAttribute(user); session.invalidate(); } response.sendRedirect(login.jsp);getSession(false)表示如果没有 session 就不再创建新对象这是退出接口和普通接口的常见区别removeAttribute删掉用户信息后invalidate()销毁整个 session这两个动作一起做才算彻底清理。否则可能出现 session 还在、但用户属性没了导致空指针的怪问题。4.3 Filter 实现未登录拦截拒绝直接访问页面一个登录页面对外提供的功能除了登录本身更重要的是“未登录不能访问其他页面”这项保护。很多学生项目只做了页面上的判断——点进某个 JSP 后发现 session 里没有用户就跳回登录页。这种做法的缺点是每个页面都要重复写判断。更工程化的做法是在web.xml里配置一个 Filter对所有请求统一拦截filter filter-nameAuthFilter/filter-name filter-classcom.demo.filter.AuthFilter/filter-class /filter filter-mapping filter-nameAuthFilter/filter-name url-pattern/user/*/url-pattern /filter-mapping这段配置的作用是把/user/路径下的所有请求交给AuthFilter处理。Filter 里的doFilter()判断 session 中是否存在用户若没有则重定向到登录页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); }这里url-pattern的粒度决定了哪些页面需要登录才能访问。源码若只把登录注册页面放在根路径业务页面统一放在/user路径下那么上面这套配置就已经算合格的小型权限控制了。若想做得更完善可以在 Filter 里加入白名单数组放行login.jsp、register.jsp、css、js等静态资源让结构更清晰。提示Filter 的init()和destroy()方法在单次部署生命周期内只调用一次适合读取配置文件、初始化连接池不要在doFilter()里反复创建数据库连接那会让性能断崖式下降。5. 避坑手册这套登录注册代码最容易踩的 5 个问题5.1 现象ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver原因驱动 jar 包没有放到项目的WEB-INF/lib目录下或者 Tomcat 与项目之间 jar 包隔离导致加载不到。解决把mssql-jdbc-x.x.x.jar复制到WebContent/WEB-INF/lib/下右键项目刷新后重启 Tomcat。如果你用 Maven还需检查pom.xml里的依赖 scope 是否为provided。5.2 现象com.microsoft.sqlserver.jdbc.SQLServerException: 通过端口 1433 连接到主机 localhost 失败原因SQL Server 默认没启用 TCP/IP 协议或者 Windows 防火墙拦截了 1433 端口。解决打开 SQL Server 配置管理器展开“SQL Server 网络配置”选中实例对应的协议右键 TCP/IP 选择“启用”不推荐直接用实例名的jdbc:sqlserver://localhost;instanceNameSQLEXPRESS来绕过问题因为实例名连接除了慢还常和老项目的sqlserver 2012 下载版本配置不一致。正确做法是把 TCP/IP 的端口固定在 1433然后重启 SQL Server 服务。5.3 现象数据库连接正常但注册的中文用户名变成“????”原因建表时字段类型用了varchar而不是nvarchar同时 JDBC 连接串里没加characterEncodingUTF-8页面、数据库、客户端三处编码不统一。解决建表统一用nvarchar连接串改为jdbc:sqlserver://localhost:1433;DatabaseNamelogin_demo;encryptfalse;characterEncodingUTF-8JSP 页面顶部声明% page contentTypetext/html;charsetUTF-8 %。这是一个“页面看是好的数据库看是问号”的血泪问题排查时建议先 SELECT 看库里的原始值确认是入库前乱码还是显示乱码。5.4 现象登录后跳转不到原页面刷新一次又回到登录页原因sendRedirect把整个页面地址重新发给了浏览器如果项目部署的上下文路径是/login_demo而重定向地址写死了login.jsp就会变成相对路径丢失上下文。解决凡是重定向统一用request.getContextPath()拼接路径比如response.sendRedirect(request.getContextPath() /login.jsp)避免项目换名字时就断链。5.5 现象IDEA/MyEclipse 里运行不报错但页面 500日志里有 UnsupportedClassVersionError原因编译项目用的 JDK 版本与 Tomcat 运行时 JDK 版本不一致。比如用 JDK 17 编译的 class扔到 JDK 8 的 Tomcat 里跑就会报“不支持 major version 61”。解决检查三处版本是否一致——项目 Properties → Java Compiler 里的编译级别、Tomcat 的 Runtime Environment 指定的 JRE、以及环境变量 JAVA_HOME 指向的 JDK。这是老项目最常见的翻车点而且报错信息往往藏在 Tomcat 的 logs 目录里不翻日志根本不知道。提示如果你下载的源码在本地怎么都起不来先别急着改代码。打开logs/catalina.out看清第一行异常是什么再动手。很多时候是端口冲突、jar 包缺失这类环境问题代码本身没毛病。6. 进阶改造从能跑到能用的三个落地点6.1 把 JDBC 连接抽成工具类并引入配置参数源码里如果每次写操作都重复DriverManager.getConnection数据库连接会频繁创建关闭并发稍高就会报连接泄漏。我把这步作为改造第一优先级新增DBUtil.java用静态代码块加载驱动连接参数统一放db.properties文件里管理。核心思路很简单驱动只加载一次连接参数可以在不重新编译的情况下修改。jdbc.drivercom.microsoft.sqlserver.jdbc.SQLServerDriver jdbc.urljdbc:sqlserver://localhost:1433;DatabaseNamelogin_demo;encryptfalse;characterEncodingUTF-8 jdbc.usernamesa jdbc.passwordyour_passwordstatic { try { InputStream in DBUtil.class.getClassLoader().getResourceAsStream(db.properties); Properties props new Properties(); props.load(in); Class.forName(props.getProperty(jdbc.driver)); URL props.getProperty(jdbc.url); USER props.getProperty(jdbc.username); PASSWORD props.getProperty(jdbc.password); } catch (Exception e) { e.printStackTrace(); } }再往上的进阶方案是引入 C3P0 或 Druid 连接池但课程设计场景里不建议动这么大更推荐的做法是保持代码结构不变只把连接创建统一入口后续换连接池时只需改动一个类。6.2 密码加密从 MD5 升级到 BCrypt很多课程设计的查重环节都会问“你这个密码加密可靠吗”MD5 在这句话面前几乎没有说服力。升级到 BCrypt 成本很低只需在pom.xml引入依赖并在注册、登录两处调用密文比对方法String hashed BCrypt.hashpw(password, BCrypt.gensalt(12)); boolean matched BCrypt.checkpw(rawPassword, hashedFromDb);需要注意 BCrypt 生成的密文每次都不一样所以密码比对绝不能用 SQL 里的而是先把用户查出来再用checkpw比对。这一步改完后之前设计的varchar(64)字段要扩容到varchar(60)以上因为 BCrypt 密文长度固定为 60。这个坑我踩过当时库表字段是 50 个字符注册直接报“String or binary data would be truncated”兜了一圈回来才发现是字段长度不够。6.3 改造源码的安全习惯顺手加入登录日志最后一件我强烈建议顺手做的事是在登录成功和失败时打一条日志记录时间、用户名、IP 地址。这对课程设计答辩是一个加分项你既能说明用户认证流程又能说清登录行为的审计思路。日志实现不需要引入 log4j 完整框架直接用java.util.logging或System.currentTimeMillis()配合简单的文件追加即可。从那以后我每次拿到别人的 JSP 项目源码都强制走一遍“建表 → 连库 → 注册 → 登录 → 翻日志”这个流程不为别的就为了在最快时间内判断这套代码能不能真正跑起来而不是只看 README 吹得天花乱坠。希望这个拆包和改造过程能帮你把这份 JSP SQL Server 登录注册源码变成自己动手改过、也敢演示的项目。本文还有配套的精品资源点击获取