企业级 Java 登录注册系统构建指南(附核心代码与配置)
目录
Java 登录注册系统完整指南
1. 系统架构概览
三层架构模式:
表现层 (Controller) → 业务逻辑层 (Service) → 数据访问层 (DAO) → 数据库 (MySQL/Oracle)
Controller
| 组件 | 职责 | 主要类 |
|---|---|---|
| Controller | 处理HTTP请求,参数校验 | UserController |
Service
| 组件 | 职责 | 主要类 |
|---|---|---|
| Service | 业务逻辑处理,事务管理 | UserService |
DAO
| 组件 | 职责 | 主要类 |
|---|---|---|
| DAO | 数据库操作 | UserDAO |
Entity
| 组件 | 职责 | 主要类 |
|---|---|---|
| Entity | 数据模型 | User |
Security
| 组件 | 职责 | 主要类 |
|---|---|---|
| Security | 安全验证,加密 | SecurityUtil |
2.数据库设计
用户表设计 (users)
CREATE TABLE users(id BIGINT PRIMARY KEY PRIMARY AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, -- 加密后的密码 salt VARCHAR(50) NOT NULL, -- 密码盐值 status TINYINT DEFAULT 1, -- 0:禁用 1:启用 create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, last_login_time TIMESTAMP NULL, -- 最后登录时间 login_attempts INT DEFAULT 0, -- 登录失败次数 locked_until TIMESTAMP NULL -- 账户锁定到期时间 );设计要点:
密码使用 BCrypt 或 SHA-256 + 盐值加密存储
增加登录失败次数控制,防止暴力破解
记录最后登录时间,便于安全审计
支持账户锁定机制
3. 核心实体类
User 实体类
importjava.io.Serializable;importjava.util.Date;importlombok.Data;importlombok.NoArgsConstructor;/** * 用户实体类 */@Data@NoArgsConstructorpublicclassUserimplementsSerializable{// 序列化版本号privatestaticfinallong serialVersionUID =1L;privateLong id;privateString username;privateString email;privateString password;privateString salt;privateInteger status;privateDate createTime;privateDate updateTime;privateDate lastLoginTime;privateInteger loginAttempts;privateDate lockedUntil;// 业务方法/** * 判断账户是否被锁定 * @return 锁定状态(true:已锁定,false:未锁定) */publicbooleanisAccountLocked(){return lockedUntil !=null&& lockedUntil.after(newDate());}/** * 判断账户是否启用 * @return 启用状态(true:启用,false:禁用) */publicbooleanisEnabled(){return status ==1;}@OverridepublicStringtoString(){return"User{id="+ id +",+ username +"'}";}}4. 安全工具类
密码加密与验证
importjava.security.MessageDigest;importjava.security.SecureRandom;importjava.util.Base64;/** * 安全工具类 * 提供密码加密、盐值生成、密码验证和Token生成等安全相关操作 */publicclassSecurityUtil{/** * 生成随机盐值(16字节) * @return Base64编码的盐值字符串 */publicstaticStringgenerateSalt(){SecureRandom random =newSecureRandom();byte[] salt =newbyte[16]; random.nextBytes(salt);returnBase64.getEncoder().encodeToString(salt);}/** * 使用SHA-256算法加密密码 * @param password 原始密码 * @param salt 盐值 * @return 加密后的密码(Base64编码) */publicstaticStringhashPassword(String password,String salt){try{MessageDigest md =MessageDigest.getInstance("SHA-256"); md.update(salt.getBytes());byte[] hashedPassword = md.digest(password.getBytes());returnBase64.getEncoder().encodeToString(hashedPassword);}catch(Exception e){thrownewRuntimeException("密码加密失败", e);}}/** * 验证密码是否匹配 * @param inputPassword 输入的原始密码 * @param storedPassword 存储的加密后密码 * @param salt 盐值 * @return 密码是否匹配(true:匹配,false:不匹配) */publicstaticbooleanverifyPassword(String inputPassword,String storedPassword,String salt){String hashedInput =hashPassword(inputPassword, salt);return hashedInput.equals(storedPassword);}/** * 生成随机Token(32字节) * 用于会话管理、身份验证等场景 * @return Base64编码的Token字符串 */publicstaticStringgenerateToken(){SecureRandom random =newSecureRandom();byte[] token =newbyte[32]; random.nextBytes(token);returnBase64.getEncoder().encodeToString(token);}}安全提示:
永远不要明文存储密码
每个密码使用独立的盐值
考虑使用更强的加密算法如 BCrypt
实施密码强度策略
5. 数据访问层 (DAO)
UserDAO 接口与实现
UserDAO.java
importjava.util.Date;/** * 用户数据访问接口 * 定义用户相关的数据库操作方法 */publicinterfaceUserDAO{/** * 根据用户名查询用户 * @param username 用户名 * @return 用户实体,不存在则返回null */UserfindByUsername(String username);/** * 根据邮箱查询用户 * @param email 邮箱地址 * @return 用户实体,不存在则返回null */UserfindByEmail(String email);/** * 保存新用户 * @param user 用户实体 * @return 保存成功返回true,否则返回false */booleansave(User user);/** * 更新用户最后登录时间 * @param userId 用户ID * @return 更新成功返回true,否则返回false */booleanupdateLastLoginTime(Long userId);/** * 更新登录失败次数 * @param userId 用户ID * @param attempts 失败次数 * @return 更新成功返回true,否则返回false */booleanupdateLoginAttempts(Long userId,int attempts);/** * 锁定用户账户 * @param userId 用户ID * @param lockUntil 锁定截止时间 * @return 操作成功返回true,否则返回false */booleanlockAccount(Long userId,Date lockUntil);}UserDAOImpl.java
importjava.sql.Connection;importjava.sql.PreparedStatement;importjava.sql.ResultSet;importjava.sql.SQLException;importjava.sql.Timestamp;importjava.util.Date;/** * 用户数据访问实现类 * 实现用户相关的数据库操作 */publicclassUserDAOImplimplementsUserDAO{privatefinalConnection connection;/** * 构造方法,注入数据库连接 * @param connection 数据库连接对象 */publicUserDAOImpl(Connection connection){this.connection = connection;}@OverridepublicUserfindByUsername(String username){String sql ="SELECT * FROM users WHERE username = ?";try(PreparedStatement stmt = connection.prepareStatement(sql)){ stmt.setString(1, username);ResultSet rs = stmt.executeQuery();if(rs.next()){returnmapResultSetToUser(rs);}}catch(SQLException e){thrownewRuntimeException("根据用户名查询用户失败: "+ username, e);}returnnull;}@OverridepublicUserfindByEmail(String email){String sql ="SELECT * FROM users WHERE email = ?";try(PreparedStatement stmt = connection.prepareStatement(sql)){ stmt.setString(1, email);ResultSet rs = stmt.executeQuery();if(rs.next()){returnmapResultSetToUser(rs);}}catch(SQLException e){thrownewRuntimeException("根据邮箱查询用户失败: "+ email, e);}returnnull;}@Overridepublicbooleansave(User user){String sql ="INSERT INTO users (username, email, password, salt) VALUES (?, ?, ?, ?)";try(PreparedStatement stmt = connection.prepareStatement(sql)){ stmt.setString(1, user.getUsername()); stmt.setString(2, user.getEmail()); stmt.setString(3, user.getPassword()); stmt.setString(4, user.getSalt());return stmt.executeUpdate()>0;}catch(SQLException e){thrownewRuntimeException("保存用户失败: "+ user.getUsername(), e);}}@OverridepublicbooleanupdateLastLoginTime(Long userId){String sql ="UPDATE users SET last_login_time = CURRENT_TIMESTAMP WHERE id = ?";try(PreparedStatement stmt = connection.prepareStatement(sql)){ stmt.setLong(1, userId);return stmt.executeUpdate()>0;}catch(SQLException e){thrownewRuntimeException("更新用户最后登录时间失败: "+ userId, e);}}@OverridepublicbooleanupdateLoginAttempts(Long userId,int attempts){String sql ="UPDATE users SET login_attempts = ?, update_time = CURRENT_TIMESTAMP WHERE id = ?";try(PreparedStatement stmt = connection.prepareStatement(sql)){ stmt.setInt(1, attempts); stmt.setLong(2, userId);return stmt.executeUpdate()>0;}catch(SQLException e){thrownewRuntimeException("更新用户登录失败次数失败: "+ userId, e);}}@OverridepublicbooleanlockAccount(Long userId,Date lockUntil){String sql ="UPDATE users SET locked_until = ?, update_time = CURRENT_TIMESTAMP WHERE id = ?";try(PreparedStatement stmt = connection.prepareStatement(sql)){ stmt.setTimestamp(1,newTimestamp(lockUntil.getTime())); stmt.setLong(2, userId);return stmt.executeUpdate()>0;}catch(SQLException e){thrownewRuntimeException("锁定用户账户失败: "+ userId, e);}}/** * 结果集映射为User实体 * @param rs 数据库结果集 * @return User实体对象 * @throws SQLException SQL异常 */privateUsermapResultSetToUser(ResultSet rs)throwsSQLException{User user =newUser(); user.setId(rs.getLong("id")); user.setUsername(rs.getString("username")); user.setEmail(rs.getString("email")); user.setPassword(rs.getString("password")); user.setSalt(rs.getString("salt")); user.setStatus(rs.getInt("status")); user.setCreateTime(rs.getTimestamp("create_time")); user.setUpdateTime(rs.getTimestamp("update_time")); user.setLastLoginTime(rs.getTimestamp("last_login_time")); user.setLoginAttempts(rs.getInt("login_attempts")); user.setLockedUntil(rs.getTimestamp("locked_until"));return user;}}6. 业务逻辑层 (Service)
UserService 核心业务逻辑
importjava.util.Date;/** * 用户服务类 * 处理用户注册、登录等核心业务逻辑 */publicclassUserService{privatefinalUserDAO userDAO;// 最大登录失败次数privatestaticfinalint MAX_LOGIN_ATTEMPTS =5;// 账户锁定时长(30分钟,单位:毫秒)privatestaticfinallong LOCK_DURATION =30*60*1000;/** * 构造方法,注入用户数据访问对象 * @param userDAO 用户数据访问接口实现 */publicUserService(UserDAO userDAO){this.userDAO = userDAO;}/** * 用户注册 * @param username 用户名 * @param email 邮箱地址 * @param password 原始密码 * @return 注册是否成功 */publicbooleanregister(String username,String email,String password){// 1. 参数合法性校验if(!isValidUsername(username)||!isValidEmail(email)||!isValidPassword(password)){thrownewIllegalArgumentException("参数格式不正确:用户名3-50位,邮箱格式正确,密码至少6位");}// 2. 检查用户名和邮箱是否已存在if(userDAO.findByUsername(username)!=null){thrownewRuntimeException("用户名已存在");}if(userDAO.findByEmail(email)!=null){thrownewRuntimeException("邮箱已存在");}// 3. 密码加密处理String salt =SecurityUtil.generateSalt();String hashedPassword =SecurityUtil.hashPassword(password, salt);// 4. 创建用户对象并保存User user =newUser(); user.setUsername(username); user.setEmail(email); user.setPassword(hashedPassword); user.setSalt(salt);return userDAO.save(user);}/** * 用户登录 * @param username 用户名 * @param password 原始密码 * @return 登录成功的用户对象 */publicUserlogin(String username,String password){// 1. 根据用户名查询用户User user = userDAO.findByUsername(username);if(user ==null){thrownewRuntimeException("用户不存在");}// 2. 检查账户状态if(!user.isEnabled()){thrownewRuntimeException("账户已被禁用");}if(user.isAccountLocked()){thrownewRuntimeException("账户已被锁定,请稍后再试");}// 3. 验证密码if(!SecurityUtil.verifyPassword(password, user.getPassword(), user.getSalt())){handleLoginFailure(user);}// 4. 登录成功处理handleLoginSuccess(user);return user;}/** * 处理登录失败逻辑 * @param user 当前登录用户 */privatevoidhandleLoginFailure(User user){int attempts = user.getLoginAttempts()+1; userDAO.updateLoginAttempts(user.getId(), attempts);// 达到最大失败次数,锁定账户if(attempts >= MAX_LOGIN_ATTEMPTS){Date lockUntil =newDate(System.currentTimeMillis()+ LOCK_DURATION); userDAO.lockAccount(user.getId(), lockUntil);thrownewRuntimeException("登录失败次数过多,账户已被锁定30分钟");}thrownewRuntimeException("密码错误,还可尝试 "+(MAX_LOGIN_ATTEMPTS - attempts)+" 次");}/** * 处理登录成功逻辑 * @param user 当前登录用户 */privatevoidhandleLoginSuccess(User user){// 重置登录失败次数 userDAO.updateLoginAttempts(user.getId(),0);// 更新最后登录时间 userDAO.updateLastLoginTime(user.getId());}/** * 校验用户名格式 * @param username 用户名 * @return 格式是否有效 */privatebooleanisValidUsername(String username){return username !=null&& username.length()>=3&& username.length()<=50;}/** * 校验邮箱格式 * @param email 邮箱地址 * @return 格式是否有效 */privatebooleanisValidEmail(String email){if(email ==null){returnfalse;}// 简单邮箱格式正则校验return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");}/** * 校验密码格式 * @param password 密码 * @return 格式是否有效 */privatebooleanisValidPassword(String password){return password !=null&& password.length()>=6;}}7. 控制器层 (Controller)
UserController - Web 层处理
importorg.springframework.http.ResponseEntity;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.servlet.http.Cookie;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjavax.servlet.http.HttpSession;importjava.util.HashMap;importjava.util.Map;/** * 用户控制器 * 处理用户注册、登录、登出等HTTP请求 */@RestController@RequestMapping("/api/user")publicclassUserController{privatefinalUserService userService;/** * 构造方法注入用户服务 * @param userService 用户服务对象 */publicUserController(UserService userService){this.userService = userService;}/** * 用户注册接口 * @param request 注册请求参数 * @param httpRequest HTTP请求对象 * @return 注册结果响应 */@PostMapping("/register")publicResponseEntity<Map<String,Object>>register(@RequestBodyRegisterRequest request,HttpServletRequest httpRequest){Map<String,Object> response =newHashMap<>();try{// 参数验证if(request.getUsername()==null|| request.getPassword()==null|| request.getEmail()==null){ response.put("success",false); response.put("message","必填参数不能为空");returnResponseEntity.badRequest().body(response);}// 调用服务层执行注册boolean success = userService.register( request.getUsername(), request.getEmail(), request.getPassword()); response.put("success", success); response.put("message", success ?"注册成功":"注册失败");returnResponseEntity.ok(response);}catch(Exception e){ response.put("success",false); response.put("message", e.getMessage());returnResponseEntity.badRequest().body(response);}}/** * 用户登录接口 * @param request 登录请求参数 * @param httpRequest HTTP请求对象 * @param httpResponse HTTP响应对象 * @return 登录结果响应 */@PostMapping("/login")publicResponseEntity<Map<String,Object>>login(@RequestBodyLoginRequest request,HttpServletRequest httpRequest,HttpServletResponse httpResponse){Map<String,Object> response =newHashMap<>();try{// 调用服务层执行登录User user = userService.login(request.getUsername(), request.getPassword());// 生成会话tokenString token =SecurityUtil.generateToken();// 设置认证CookieCookie cookie =newCookie("auth_token", token); cookie.setHttpOnly(true); cookie.setSecure(true);// 生产环境使用HTTPS时启用 cookie.setMaxAge(24*60*60);// 24小时有效期 cookie.setPath("/");// 设置Cookie路径 httpResponse.addCookie(cookie);// 将用户信息存储到sessionHttpSession session = httpRequest.getSession(); session.setAttribute("user_id", user.getId()); session.setAttribute("username", user.getUsername());// 构建响应数据 response.put("success",true); response.put("message","登录成功"); response.put("user",Map.of("id", user.getId(),"username", user.getUsername(),"email", user.getEmail()));returnResponseEntity.ok(response);}catch(Exception e){ response.put("success",false); response.put("message", e.getMessage());returnResponseEntity.badRequest().body(response);}}/** * 用户登出接口 * @param request HTTP请求对象 * @param response HTTP响应对象 * @return 登出结果响应 */@PostMapping("/logout")publicResponseEntity<Map<String,Object>>logout(HttpServletRequest request,HttpServletResponse response){// 清除sessionHttpSession session = request.getSession(false);if(session !=null){ session.invalidate();}// 清除认证CookieCookie cookie =newCookie("auth_token",null); cookie.setMaxAge(0); cookie.setPath("/"); response.addCookie(cookie);Map<String,Object> result =newHashMap<>(); result.put("success",true); result.put("message","退出成功");returnResponseEntity.ok(result);}}/** * 注册请求DTO * 接收注册接口的请求参数 */classRegisterRequest{privateString username;privateString email;privateString password;// Getter和Setter方法publicStringgetUsername(){return username;}publicvoidsetUsername(String username){this.username = username;}publicStringgetEmail(){return email;}publicvoidsetEmail(String email){this.email = email;}publicStringgetPassword(){return password;}publicvoidsetPassword(String password){this.password = password;}}/** * 登录请求DTO * 接收登录接口的请求参数 */classLoginRequest{privateString username;privateString password;// Getter和Setter方法publicStringgetUsername(){return username;}publicvoidsetUsername(String username){this.username = username;}publicStringgetPassword(){return password;}publicvoidsetPassword(String password){this.password = password;}}配置文件
# Spring Boot 主配置文件 spring: # 应用基本信息 application: name: bushuousers # 应用名称# 环境配置 (dev/test/prod,当前激活开发环境) profiles: active: dev # 数据源配置(MySQL) datasource: driver-class-name: com.mysql.cj.jdbc.Driver # MySQL 8.x 驱动类 url: jdbc:mysql://localhost:3306/bushuo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root # 数据库用户名,默认 root(支持环境变量注入) password: 123456# 数据库密码,默认 123456(支持环境变量注入)# Hikari 连接池配置(Spring Boot 默认连接池) hikari: minimum-idle: 5# 最小空闲连接数 maximum-pool-size: 20# 最大连接数 auto-commit: true# 自动提交事务 idle-timeout: 30000# 空闲连接超时时间(30秒) pool-name: UserAuthHikariCP # 连接池名称 max-lifetime: 1800000# 连接最大生命周期(30分钟) connection-timeout: 30000# 连接获取超时时间(30秒)# JPA/Hibernate 配置(ORM 框架) jpa: hibernate: ddl-auto: update # 表结构自动更新(开发环境用,生产环境建议改为 validate) show-sql: true# 控制台打印 SQL 语句 properties: hibernate: dialect: org.hibernate.dialect.MySQL8Dialect # MySQL 8.x 方言 format_sql: true# 格式化 SQL 语句(便于调试)# Redis 配置(用于会话存储、缓存) redis: host: localhost # Redis 服务地址 port: 6379# Redis 端口# Redis 密码(默认空,支持环境变量注入) database: 0# Redis 数据库索引(默认 0) timeout: 2000ms # 连接超时时间(2秒)# Jedis 连接池配置 jedis: pool: max-active: 20# 最大活跃连接数 max-idle: 10# 最大空闲连接数 min-idle: 5# 最小空闲连接数 max-wait: 2000ms # 连接获取最大等待时间(2秒)# 会话配置(基于 Redis 存储会话) session: store-type: redis # 会话存储类型(redis/memory/jdbc) timeout: 1800s # 会话超时时间(30分钟) redis: namespace: "user:session"# Redis 会话键前缀(避免键冲突)# Web 配置 mvc: pathmatch: matching-strategy: ant_path_matcher # 路径匹配策略(兼容老版本 Ant 风格) web: resources: static-locations: classpath:/static/ # 静态资源路径(CSS/JS/图片等) cache-period: 3600# 静态资源缓存时间(1小时)# 邮件配置(用于注册验证、密码找回)# mail:# host: smtp.qq.com # 邮件服务器地址(QQ 邮箱示例)# port: 587 # 邮件服务器端口(TLS 协议默认 587)# username: ${MAIL_USERNAME:[email protected]} # 发件人邮箱(支持环境变量注入)# password: ${MAIL_PASSWORD:your-auth-code} # 邮箱授权码(非登录密码,支持环境变量注入)# properties:# mail:# smtp:# auth: true # 启用 SMTP 认证# starttls:# enable: true # 启用 STARTTLS 加密(安全传输)# 服务器配置 server: port: 8080# 应用端口 servlet: context-path: / # 应用上下文路径(默认 /,如需前缀可改为 /api 等) session: timeout: 30m # 服务器会话超时时间(与 Spring 会话超时保持一致) cookie: http-only: true# 启用 HttpOnly(防止 JS 读取 Cookie,提升安全性) secure: false# 启用 Secure(仅 HTTPS 传输 Cookie,生产环境建议设为 true) max-age: 1800# Cookie 最大存活时间(30分钟,与会话超时保持一致)# 错误页面配置 error: whitelabel: enabled: false# 禁用默认错误页面(自定义错误页面时需关闭) include-message: always # 错误响应中包含错误信息 include-binding-errors: always # 错误响应中包含参数绑定错误# 日志配置 logging: level: # 日志级别(DEBUG < INFO < WARN < ERROR) com.example.demo: DEBUG # 自定义包日志级别(调试用) org.springframework.security: DEBUG # Spring Security 日志级别(调试权限问题用) org.springframework.web: INFO # Spring Web 日志级别 org.hibernate.SQL: DEBUG # Hibernate SQL 日志级别(打印执行的 SQL) org.hibernate.type.descriptor.sql.BasicBinder: TRACE # 打印 SQL 参数(调试参数绑定用) pattern: # 日志格式 console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"# 控制台日志格式(简洁版) file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"# 日志文件格式(含线程、日志器) file: # 日志文件配置 name: logs/user-auth.log # 日志文件路径及名称(项目根目录下 logs 文件夹) max-size: 10MB # 单个日志文件最大大小(超过后切割) max-history: 30# 日志文件保留历史天数(30天)# 应用自定义配置(业务相关配置,可通过 @ConfigurationProperties 注入)controller → service → mapper → 数据库
(前端请求) (业务处理) (数据访问)
model贯穿各层,用于数据传递;utils为各层提供通用支持。
可能与最终版本可能有差距,但是不急。gitee
请等待后续扩展功能
▼
常见的扩展功能:
邮箱验证:注册后发送验证邮件
找回密码:通过邮箱重置密码
记住我:使用持久化Cookie
多因素认证:短信验证码、Google Authenticator
第三方登录:微信、QQ、微博登录
用户权限:角色权限管理系统
设备管理:记录登录设备,异地登录提醒
性能优化:
缓存:使用 Redis 缓存用户信息
连接池:数据库连接池管理
分库分表:用户数据分片存储
异步处理:日志记录异步化