
本文将详细介绍一个基于Java Servlet的Web应用中的用户登录系统的设计与实现。该系统包含完整的JavaBean、DAO层、Servlet控制器和前端JSP页面。
1. 系统架构设计
1.1 三层架构
系统采用经典的三层架构:
-
表示层:JSP页面,负责用户界面展示
-
控制层:Servlet,负责请求处理和业务逻辑控制
-
数据访问层:DAO类,负责数据库操作
1.2 组件关系图
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ JSP页面 │────>│ Servlet │────>│ DAO层 │────>│ MySQL数据库│
│ (login.jsp) │ │ (LoginServlet)│ │ (UserDAO) │ │ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
↑ ↑ ↑
│ │ │
└────────────────────┼────────────────────┘
Session管理 │ 实体类传递
│ (User.java)
│
┌─────────────┐
│ JavaBean │
│ (User类) │
└─────────────┘
2. 核心组件详解
2.1 实体类:User.java
package org.example;
import java.sql.Timestamp;
public class User {
private int id; // 用户ID,主键
private String username; // 用户名
private String password; // 密码
private String email; // 邮箱
private Timestamp createdAt; // 创建时间
// 无参构造函数(必须)
public User() {
}
// 带参数的构造函数(用于快速创建对象)
public User(String username, String password) {
this.username = username;
this.password = password;
}
// Getter和Setter方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Timestamp getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Timestamp createdAt) {
this.createdAt = createdAt;
}
}
设计说明:
-
遵循JavaBean规范,提供无参构造函数
-
所有属性私有化,通过getter/setter访问
-
包含基础的验证逻辑需要的字段
2.2 数据访问层:UserDAO.java
package org.example;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class UserDAO {
// 数据库连接配置
private String jdbcURL = "jdbc:mysql://8.138.244.85:3306/shop?useSSL=false&serverTimezone=UTC";
private String jdbcUsername = "admin";
private String jdbcPassword = "sun123";
// SQL语句常量定义
private static final String SELECT_ALL_USERS = "SELECT id, username, password, email, created_at FROM users ORDER BY id DESC";
private static final String SELECT_USER_BY_ID = "SELECT id, username, password, email, created_at FROM users WHERE id = ?";
private static final String INSERT_USER = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)";
private static final String UPDATE_USER = "UPDATE users SET username = ?, password = ?, email = ? WHERE id = ?";
private static final String DELETE_USER = "DELETE FROM users WHERE id = ?";
private static final String SELECT_USER_BY_USERNAME = "SELECT id, username, password, email FROM users WHERE username = ?";
// 数据库连接方法
protected Connection getConnection() {
Connection connection = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection(jdbcURL, jdbcUsername, jdbcPassword);
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
}
return connection;
}
// 用户验证方法(核心方法)
public boolean validate(User user) {
boolean status = false;
try (Connection connection = getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(
"SELECT * FROM users WHERE username = ? AND password = ?")) {
preparedStatement.setString(1, user.getUsername());
preparedStatement.setString(2, user.getPassword());
ResultSet rs = preparedStatement.executeQuery();
status = rs.next(); // 如果查询到结果,返回true
} catch (SQLException e) {
e.printStackTrace();
}
return status;
}
// 其他CRUD操作方法...
// 省略了selectAllUsers、insertUser、updateUser等方法
}
安全考虑:
-
使用PreparedStatement防止SQL注入
-
使用try-with-resources确保资源自动关闭
-
密码存储应考虑加密(实际项目中应使用加密存储)
2.3 控制器:LoginServlet.java
package org.example;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private UserDAO userDAO;
// 初始化方法
public void init() {
userDAO = new UserDAO();
}
// POST请求处理方法
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. 获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
// 2. 创建用户对象
User user = new User();
user.setUsername(username);
user.setPassword(password);
try {
// 3. 验证用户凭据
if (userDAO.validate(user)) {
// 4. 创建会话并设置属性
HttpSession session = request.getSession();
session.setAttribute("loggedIn", "true");
session.setAttribute("username", username);
// 5. 登录成功,重定向到管理页面
response.sendRedirect("admin.jsp");
} else {
// 6. 登录失败,重定向回登录页并传递错误参数
response.sendRedirect("login.jsp?error=true");
}
} catch (Exception e) {
e.printStackTrace();
// 7. 异常处理,重定向到错误页面
response.sendRedirect("error.jsp");
}
}
}
会话管理:
-
使用HttpSession存储用户登录状态
-
防止会话固定攻击(实际项目应考虑session regeneration)
2.4 前端页面:login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户登录</title>
<style>
/* 整体布局样式 */
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
/* 登录容器样式 */
.login-container {
background-color: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
width: 300px;
}
h2 {
text-align: center;
color: #333;
}
/* 表单组样式 */
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
color: #555;
}
/* 输入框样式 */
input[type="text"],
input[type="password"] {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
/* 按钮样式 */
button {
width: 100%;
padding: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
/* 错误信息样式 */
.error {
color: red;
text-align: center;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="login-container">
<h2>用户登录</h2>
<!-- 登录表单 -->
<form action="login" method="post">
<div class="form-group">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">登录</button>
</form>
<!-- 错误信息显示 -->
<%
String error = request.getParameter("error");
if (error != null) {
%>
<div class="error">登录失败,请检查用户名和密码</div>
<% } %>
</div>
</body>
</html>
前端特点:
-
响应式设计,居中布局
-
表单验证使用HTML5的required属性
-
通过JSP脚本动态显示错误信息
-
简洁美观的CSS样式
3. 配置文件
3.1 web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- 配置登录Servlet -->
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>org.example.LoginServlet</servlet-class>
</servlet>
<!-- Servlet映射 -->
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<!-- 欢迎页面 -->
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
<!-- 会话超时配置 -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
4. 数据库设计
4.1 用户表结构
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status TINYINT DEFAULT 1 COMMENT '1-正常, 0-禁用'
);
-- 创建索引
CREATE INDEX idx_username ON users(username);
CREATE INDEX idx_email ON users(email);
5. 安全考虑与改进建议
5.1 当前实现的安全问题
-
密码明文存储:数据库中存储明文密码
-
缺乏密码复杂度验证
-
没有防止暴力破解机制
-
缺少CSRF防护
5.2 改进建议
5.2.1 密码加密存储
// 使用BCrypt加密密码
import org.mindrot.jbcrypt.BCrypt;
public class PasswordUtil {
// 密码加密
public static String hashPassword(String plainPassword) {
return BCrypt.hashpw(plainPassword, BCrypt.gensalt(12));
}
// 密码验证
public static boolean checkPassword(String plainPassword, String hashedPassword) {
return BCrypt.checkpw(plainPassword, hashedPassword);
}
}
5.2.2 增强的UserDAO验证方法
public User validateWithEncryption(String username, String plainPassword) {
User user = null;
String sql = "SELECT id, username, password, email FROM users WHERE username = ? AND status = 1";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
String storedHash = rs.getString("password");
// 验证密码(使用BCrypt)
if (PasswordUtil.checkPassword(plainPassword, storedHash)) {
user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setEmail(rs.getString("email"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return user;
}
5.2.3 添加登录限制机制
public class LoginLimiter {
private static final Map<String, LoginAttempt> attempts = new ConcurrentHashMap<>();
private static final int MAX_ATTEMPTS = 5;
private static final long LOCK_TIME = 15 * 60 * 1000; // 15分钟
public static boolean isLocked(String username) {
LoginAttempt attempt = attempts.get(username);
if (attempt == null) return false;
if (attempt.isLocked() &&
(System.currentTimeMillis() - attempt.getLockTime()) < LOCK_TIME) {
return true;
}
if (attempt.isLocked() &&
(System.currentTimeMillis() - attempt.getLockTime()) >= LOCK_TIME) {
attempts.remove(username); // 解锁
return false;
}
return false;
}
public static void recordFailedAttempt(String username) {
LoginAttempt attempt = attempts.getOrDefault(username, new LoginAttempt());
attempt.increment();
if (attempt.getCount() >= MAX_ATTEMPTS) {
attempt.lock();
}
attempts.put(username, attempt);
}
public static void clearAttempts(String username) {
attempts.remove(username);
}
}
6. 扩展功能建议
6.1 添加记住我功能
// 在LoginServlet中添加Cookie处理
if ("on".equals(request.getParameter("remember"))) {
Cookie usernameCookie = new Cookie("rememberedUser", username);
usernameCookie.setMaxAge(30 * 24 * 60 * 60); // 30天
usernameCookie.setHttpOnly(true);
response.addCookie(usernameCookie);
}
6.2 添加验证码功能
// 生成验证码
public class CaptchaUtil {
public static String generateCaptcha() {
// 生成4位随机数字
Random random = new Random();
return String.format("%04d", random.nextInt(10000));
}
public static BufferedImage generateCaptchaImage(String captchaText) {
// 创建验证码图片
// 实现图片生成逻辑
return null;
}
}
7. 总结
本文详细介绍了基于Java Servlet的用户登录系统的设计与实现。系统采用经典的三层架构,具有良好的扩展性和维护性。虽然当前实现是一个基础版本,但提供了完整的工作流程和清晰的代码结构。
核心优势:
-
清晰的架构分层
-
完整的错误处理机制
-
良好的用户体验设计
-
易于扩展和维护
需要改进的地方:
-
密码安全性需要加强
-
添加更多的安全防护机制
-
考虑分布式部署时的会话管理
此系统可以作为学习Java Web开发的良好起点,也可以作为实际项目的基础框架进行扩展。
