跳到主要内容企业级 Java 登录注册系统构建指南:核心代码与配置 | 极客日志Javajava
企业级 Java 登录注册系统构建指南:核心代码与配置
企业级 Java 登录注册系统采用三层架构设计,涵盖数据库模型、安全加密、业务逻辑及 Web 接口实现。通过 BCrypt 或 SHA-256 加盐存储密码,结合登录失败锁定机制保障账户安全。文章提供完整的 Spring Boot 配置示例与核心代码片段,适用于生产环境参考。
企业级 Java 登录注册系统构建指南
1. 系统架构概览
本系统采用经典的三层架构模式,确保业务逻辑清晰、可维护性强。
- 表现层 (Controller):处理 HTTP 请求,参数校验,返回 JSON 响应。
- 业务逻辑层 (Service):核心业务处理,事务管理,安全策略执行。
- 数据访问层 (DAO):直接操作数据库,封装 SQL 逻辑。
- 实体层 (Entity):对应数据库表结构的数据模型。
- 安全层 (Security):负责密码加密、盐值生成及 Token 管理。
组件职责对照
| 层级 | 主要职责 | 核心类 |
|---|
| Controller | 接收请求,参数校验 | UserController |
| Service | 业务流转,事务控制 | UserService |
| DAO | 持久化操作 | UserDAO |
| Entity | 数据映射 | User |
| Security | 加密验证 | SecurityUtil |
2. 数据库设计
用户表是系统的核心,设计时需考虑安全性与扩展性。以下 SQL 基于 MySQL 8.x。
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 COMMENT '加密后的密码',
salt VARCHAR(50) NOT NULL COMMENT '密码盐值',
status TINYINT DEFAULT COMMENT ,
create_time ,
update_time ,
last_login_time COMMENT ,
login_attempts COMMENT ,
locked_until COMMENT
);
1
'0:禁用 1:启用'
TIMESTAMP
DEFAULT
CURRENT_TIMESTAMP
TIMESTAMP
DEFAULT
CURRENT_TIMESTAMP
ON
UPDATE
CURRENT_TIMESTAMP
TIMESTAMP
NULL
'最后登录时间'
INT
DEFAULT
0
'登录失败次数'
TIMESTAMP
NULL
'账户锁定到期时间'
设计要点
- 密码必须使用 BCrypt 或 SHA-256 + 盐值加密存储,严禁明文。
- 增加
login_attempts 和 locked_until 字段,防止暴力破解。
- 记录
last_login_time 便于安全审计。
3. 核心实体类
User 实体类需实现 Serializable 接口以便会话存储,同时利用 Lombok 简化代码。
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. 安全工具类
密码安全是重中之重。这里提供基础的加盐哈希实现,生产环境建议直接使用 Spring Security 的 BCryptEncoder。
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);
}
}
安全提示
- 永远不要明文存储密码。
- 每个密码必须使用独立的盐值。
- 实施密码强度策略。
5. 数据访问层 (DAO)
DAO 层负责与数据库交互,这里使用原生 JDBC 演示核心逻辑,实际项目中常配合 MyBatis 或 JPA。
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);
}
UserDAOImpl 实现
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)
Service 层汇聚了注册、登录的核心逻辑,包括参数校验、状态检查和异常处理。
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)
Web 层负责接收前端请求并调用 Service 层,同时处理 Session 和 Cookie。
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
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);
}
static 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; }
}
static 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; }
}
}
8. 配置文件
Spring Boot 主配置文件 application.yml,涵盖数据源、连接池及基础安全设置。
spring:
application:
name: bushuo-users
profiles:
active: dev
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/bushuo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
hikari:
minimum-idle: 5
maximum-pool-size: 20
auto-commit: true
idle-timeout: 30000
pool-name: UserAuthHikariCP
max-lifetime: 1800000
connection-timeout: 30000
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
redis:
host: localhost
port: 6379
database: 0
timeout: 2000ms
jedis:
pool:
max-active: 20
max-idle: 10
min-idle: 5
max-wait: 2000ms
session:
store-type: redis
timeout: 1800s
redis:
namespace: "user:session"
mvc:
pathmatch:
matching-strategy: ant_path_matcher
web:
resources:
static-locations: classpath:/static/
cache-period: 3600
server:
port: 8080
servlet:
context-path: /
session:
timeout: 30m
cookie:
http-only: true
secure: false
max-age: 1800
error:
whitelabel:
enabled: false
include-message: always
include-binding-errors: always
logging:
level:
com.example.demo: DEBUG
org.springframework.security: DEBUG
org.springframework.web: INFO
org.hibernate.SQL: DEBUG
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
max-size: 10MB
max-history: 30
以上架构涵盖了从数据库到 Web 接口的完整链路。在实际落地时,建议结合 Spring Security 框架进一步加固认证授权机制,并根据业务需求引入 Redis 缓存优化性能。
相关免费在线工具
- 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