跳到主要内容
Java 登录注册系统构建指南:核心代码与配置 | 极客日志
Java java
Java 登录注册系统构建指南:核心代码与配置 综述由AI生成 基于 Java Spring Boot 的企业级登录注册系统构建过程。内容涵盖系统三层架构设计(Controller、Service、DAO、Entity),用户数据库表结构设计及安全策略(BCrypt/SHA-256 加密、盐值、账户锁定)。提供了核心实体类、安全工具类、数据访问层、业务逻辑层及控制器的完整代码实现,包含注册、登录、登出接口及会话管理。此外,还给出了 Spring Boot 配置文件详解,涉及数据源、连接池、Redis 会话存储及日志配置,为开发安全稳定的用户认证模块提供参考。
星云 发布于 2026/3/29 更新于 2026/5/29 34 浏览Java 登录注册系统完整指南
1. 系统架构概览
采用三层架构模式:表现层 (Controller) → 业务逻辑层 (Service) → 数据访问层 (DAO) → 数据库 (MySQL/Oracle)
组件 职责 主要类 Controller 处理 HTTP 请求,参数校验 UserController Service 业务逻辑处理,事务管理 UserService DAO 数据库操作 UserDAO Entity 数据模型 User Security 安全验证,加密 SecurityUtil
2. 数据库设计
用户表设计 (users)
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
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 ,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ,
last_login_time TIMESTAMP ,
login_attempts ,
locked_until
);
NULL
INT
DEFAULT
0
TIMESTAMP
NULL
设计要点:
密码使用 BCrypt 或 SHA-256 + 盐值加密存储
增加登录失败次数控制,防止暴力破解
记录最后登录时间,便于安全审计
支持账户锁定机制
3. 核心实体类
User 实体类 import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 1L ;
private Long id;
private String username;
private String email;
private String password;
private String salt;
private Integer status;
private Date createTime;
private Date updateTime;
private Date lastLoginTime;
private Integer loginAttempts;
private Date lockedUntil;
public boolean isAccountLocked () {
return lockedUntil != null && lockedUntil.after(new Date ());
}
public boolean isEnabled () {
return status == 1 ;
}
@Override
public String toString () {
return "User{id=" + id + ", username='" + username + "'}" ;
}
}
4. 安全工具类
密码加密与验证 import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
public class SecurityUtil {
public static String generateSalt () {
SecureRandom random = new SecureRandom ();
byte [] salt = new byte [16 ];
random.nextBytes(salt);
return Base64.getEncoder().encodeToString(salt);
}
public static String hashPassword (String password, String salt) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256" );
md.update(salt.getBytes());
byte [] hashedPassword = md.digest(password.getBytes());
return Base64.getEncoder().encodeToString(hashedPassword);
} catch (Exception e) {
throw new RuntimeException ("密码加密失败" , e);
}
}
public static boolean verifyPassword (String inputPassword, String storedPassword, String salt) {
String hashedInput = hashPassword(inputPassword, salt);
return hashedInput.equals(storedPassword);
}
public static String generateToken () {
SecureRandom random = new SecureRandom ();
byte [] token = new byte [32 ];
random.nextBytes(token);
return Base64.getEncoder().encodeToString(token);
}
}
安全提示:
永远不要明文存储密码
每个密码使用独立的盐值
考虑使用更强的加密算法如 BCrypt
实施密码强度策略
5. 数据访问层 (DAO)
UserDAO 接口与实现 import java.util.Date;
public interface UserDAO {
User findByUsername (String username) ;
User findByEmail (String email) ;
boolean save (User user) ;
boolean updateLastLoginTime (Long userId) ;
boolean updateLoginAttempts (Long userId, int attempts) ;
boolean lockAccount (Long userId, Date lockUntil) ;
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;
public class UserDAOImpl implements UserDAO {
private final Connection connection;
public UserDAOImpl (Connection connection) {
this .connection = connection;
}
@Override
public User findByUsername (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()) {
return mapResultSetToUser(rs);
}
} catch (SQLException e) {
throw new RuntimeException ("根据用户名查询用户失败:" + username, e);
}
return null ;
}
@Override
public User findByEmail (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()) {
return mapResultSetToUser(rs);
}
} catch (SQLException e) {
throw new RuntimeException ("根据邮箱查询用户失败:" + email, e);
}
return null ;
}
@Override
public boolean save (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) {
throw new RuntimeException ("保存用户失败:" + user.getUsername(), e);
}
}
@Override
public boolean updateLastLoginTime (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) {
throw new RuntimeException ("更新用户最后登录时间失败:" + userId, e);
}
}
@Override
public boolean updateLoginAttempts (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) {
throw new RuntimeException ("更新用户登录失败次数失败:" + userId, e);
}
}
@Override
public boolean lockAccount (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 , new Timestamp (lockUntil.getTime()));
stmt.setLong(2 , userId);
return stmt.executeUpdate() > 0 ;
} catch (SQLException e) {
throw new RuntimeException ("锁定用户账户失败:" + userId, e);
}
}
private User mapResultSetToUser (ResultSet rs) throws SQLException {
User user = new User ();
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 核心业务逻辑 import java.util.Date;
public class UserService {
private final UserDAO userDAO;
private static final int MAX_LOGIN_ATTEMPTS = 5 ;
private static final long LOCK_DURATION = 30 * 60 * 1000 ;
public UserService (UserDAO userDAO) {
this .userDAO = userDAO;
}
public boolean register (String username, String email, String password) {
if (!isValidUsername(username) || !isValidEmail(email) || !isValidPassword(password)) {
throw new IllegalArgumentException ("参数格式不正确:用户名 3-50 位,邮箱格式正确,密码至少 6 位" );
}
if (userDAO.findByUsername(username) != null ) {
throw new RuntimeException ("用户名已存在" );
}
if (userDAO.findByEmail(email) != null ) {
throw new RuntimeException ("邮箱已存在" );
}
String salt = SecurityUtil.generateSalt();
String hashedPassword = SecurityUtil.hashPassword(password, salt);
User user = new User ();
user.setUsername(username);
user.setEmail(email);
user.setPassword(hashedPassword);
user.setSalt(salt);
return userDAO.save(user);
}
public User login (String username, String password) {
User user = userDAO.findByUsername(username);
if (user == null ) {
throw new RuntimeException ("用户不存在" );
}
if (!user.isEnabled()) {
throw new RuntimeException ("账户已被禁用" );
}
if (user.isAccountLocked()) {
throw new RuntimeException ("账户已被锁定,请稍后再试" );
}
if (!SecurityUtil.verifyPassword(password, user.getPassword(), user.getSalt())) {
handleLoginFailure(user);
}
handleLoginSuccess(user);
return user;
}
private void handleLoginFailure (User user) {
int attempts = user.getLoginAttempts() + 1 ;
userDAO.updateLoginAttempts(user.getId(), attempts);
if (attempts >= MAX_LOGIN_ATTEMPTS) {
Date lockUntil = new Date (System.currentTimeMillis() + LOCK_DURATION);
userDAO.lockAccount(user.getId(), lockUntil);
throw new RuntimeException ("登录失败次数过多,账户已被锁定 30 分钟" );
}
throw new RuntimeException ("密码错误,还可尝试 " + (MAX_LOGIN_ATTEMPTS - attempts) + " 次" );
}
private void handleLoginSuccess (User user) {
userDAO.updateLoginAttempts(user.getId(), 0 );
userDAO.updateLastLoginTime(user.getId());
}
private boolean isValidUsername (String username) {
return username != null && username.length() >= 3 && username.length() <= 50 ;
}
private boolean isValidEmail (String email) {
if (email == null ) {
return false ;
}
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$" );
}
private boolean isValidPassword (String password) {
return password != null && password.length() >= 6 ;
}
}
7. 控制器层 (Controller)
UserController - Web 层处理 import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
public UserController (UserService userService) {
this .userService = userService;
}
@PostMapping("/register")
public ResponseEntity<Map<String, Object>> register (@RequestBody RegisterRequest request, HttpServletRequest httpRequest) {
Map<String, Object> response = new HashMap <>();
try {
if (request.getUsername() == null || request.getPassword() == null || request.getEmail() == null ) {
response.put("success" , false );
response.put("message" , "必填参数不能为空" );
return ResponseEntity.badRequest().body(response);
}
boolean success = userService.register(request.getUsername(), request.getEmail(), request.getPassword());
response.put("success" , success);
response.put("message" , success ? "注册成功" : "注册失败" );
return ResponseEntity.ok(response);
} catch (Exception e) {
response.put("success" , false );
response.put("message" , e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login (@RequestBody LoginRequest request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
Map<String, Object> response = new HashMap <>();
try {
User user = userService.login(request.getUsername(), request.getPassword());
String token = SecurityUtil.generateToken();
Cookie cookie = new Cookie ("auth_token" , token);
cookie.setHttpOnly(true );
cookie.setSecure(true );
cookie.setMaxAge(24 * 60 * 60 );
cookie.setPath("/" );
httpResponse.addCookie(cookie);
HttpSession 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()));
return ResponseEntity.ok(response);
} catch (Exception e) {
response.put("success" , false );
response.put("message" , e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@PostMapping("/logout")
public ResponseEntity<Map<String, Object>> logout (HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession(false );
if (session != null ) {
session.invalidate();
}
Cookie cookie = new Cookie ("auth_token" , null );
cookie.setMaxAge(0 );
cookie.setPath("/" );
response.addCookie(cookie);
Map<String, Object> result = new HashMap <>();
result.put("success" , true );
result.put("message" , "退出成功" );
return ResponseEntity.ok(result);
}
}
class RegisterRequest {
private String username;
private String email;
private String password;
public String getUsername () { return username; }
public void setUsername (String username) { this .username = username; }
public String getEmail () { return email; }
public void setEmail (String email) { this .email = email; }
public String getPassword () { return password; }
public void setPassword (String password) { this .password = password; }
}
class LoginRequest {
private String username;
private String password;
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; }
}
配置文件 # Spring Boot 主配置文件
spring:
# 应用基本信息
application:
name: bushuo-users # 应用名称
# 环境配置 (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 # 数据库用户名
password: 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 端口
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 天)
controller → service → mapper → 数据库
(前端请求) (业务处理) (数据访问)
model 贯穿各层,用于数据传递;utils 为各层提供通用支持。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online