用户注册登录是绝大多数Java Web应用的基础功能,其中输入验证用于过滤非法用户提交的数据,密码加密则能避免用户密码明文存储带来的泄露风险,两者共同保障系统的数据安全和用户账号安全。

一、用户注册登录的整体流程
完整的注册登录流程通常包含以下步骤:
- 用户提交注册信息,后端先对输入数据做合法性校验
- 校验通过后,对密码进行加密处理,将用户信息存入数据库
- 用户提交登录信息,后端校验账号是否存在、密码是否匹配
- 登录成功后生成会话或令牌,返回给前端用于后续身份校验
二、输入验证的实现方法
输入验证需要覆盖多个维度,避免SQL注入、XSS攻击以及无效数据入库,常见的验证场景包括非空校验、长度校验、格式校验等。
1. 基础输入验证示例
可以通过自定义工具类实现通用的输入校验逻辑,以下是简单的校验工具类代码:
import java.util.regex.Pattern;
public class InputValidator {
// 邮箱正则
private static final String EMAIL_REGEX = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
// 手机号正则(国内11位)
private static final String PHONE_REGEX = "^1[3-9]\d{9}$";
/**
* 校验字符串非空且长度在指定范围内
* @param str 待校验字符串
* @param min 最小长度
* @param max 最大长度
* @return 校验结果
*/
public static boolean validateLength(String str, int min, int max) {
if (str == null) {
return false;
}
int length = str.trim().length();
return length >= min && length <= max;
}
/**
* 校验邮箱格式是否合法
* @param email 待校验邮箱
* @return 校验结果
*/
public static boolean validateEmail(String email) {
if (email == null) {
return false;
}
return Pattern.matches(EMAIL_REGEX, email.trim());
}
/**
* 校验手机号格式是否合法
* @param phone 待校验手机号
* @return 校验结果
*/
public static boolean validatePhone(String phone) {
if (phone == null) {
return false;
}
return Pattern.matches(PHONE_REGEX, phone.trim());
}
}
2. 结合Spring Validation的校验
如果使用Spring Boot框架,可以直接使用@Valid注解配合校验注解简化开发,以下是用户注册DTO的示例:
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
public class UserRegisterDTO {
@NotBlank(message = "用户名不能为空")
@Size(min = 4, max = 20, message = "用户名长度需要在4到20位之间")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 8, max = 32, message = "密码长度需要在8到32位之间")
private String password;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
// getter和setter方法省略
}
在Controller层添加@Valid注解后,框架会自动完成参数校验,校验失败会抛出对应异常,开发者可以统一处理异常返回提示信息。
三、密码加密的实现方法
明文存储密码是极大的安全隐患,一旦数据库泄露,用户所有账号都会面临风险,因此必须对密码进行加密存储。早期的MD5、SHA等哈希算法因为可以通过彩虹表破解,已经不再适合单独用于密码加密,目前推荐使用BCrypt算法。
1. BCrypt加密的优势
- 自带盐值生成,每次加密的盐值都不同,相同密码加密后的结果也不同
- 可以配置加密成本,随着硬件性能提升可以调整成本参数增加破解难度
- 内置了防时序攻击的逻辑,安全性更高
2. BCrypt的使用示例
首先需要引入BCrypt的依赖,如果是Maven项目,在pom.xml中添加以下依赖:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>5.7.2</version>
</dependency>
以下是密码加密和校验的完整代码示例:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordEncoderDemo {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = "User123456"; // 用户提交的明文密码
// 加密密码,存入数据库
String encodedPassword = encoder.encode(rawPassword);
System.out.println("加密后的密码:" + encodedPassword);
// 登录时校验密码,从数据库取出encodedPassword,和用户输入的rawPassword比对
boolean isMatch = encoder.matches(rawPassword, encodedPassword);
System.out.println("密码是否匹配:" + isMatch);
// 校验错误密码的情况
boolean wrongMatch = encoder.matches("WrongPassword", encodedPassword);
System.out.println("错误密码是否匹配:" + wrongMatch);
}
}
3. 注册登录中密码处理的完整逻辑
结合前面的输入验证和BCrypt加密,完整的注册登录服务层代码如下:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 假设存在用户数据库操作的Mapper,这里省略定义
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
/**
* 用户注册逻辑
* @param registerDTO 注册参数
* @return 注册结果
*/
public String register(UserRegisterDTO registerDTO) {
// 1. 输入验证(如果使用Spring Validation,这里可以省略手动校验)
if (!InputValidator.validateLength(registerDTO.getUsername(), 4, 20)) {
return "用户名长度不符合要求";
}
if (!InputValidator.validateEmail(registerDTO.getEmail())) {
return "邮箱格式不正确";
}
// 2. 校验账号是否已存在
User existUser = userMapper.selectByUsername(registerDTO.getUsername());
if (existUser != null) {
return "用户名已存在";
}
// 3. 密码加密
String encodedPassword = passwordEncoder.encode(registerDTO.getPassword());
// 4. 构建用户对象存入数据库
User user = new User();
user.setUsername(registerDTO.getUsername());
user.setPassword(encodedPassword);
user.setEmail(registerDTO.getEmail());
userMapper.insert(user);
return "注册成功";
}
/**
* 用户登录逻辑
* @param username 用户名
* @param password 明文密码
* @return 登录结果
*/
public String login(String username, String password) {
// 1. 查询用户信息
User user = userMapper.selectByUsername(username);
if (user == null) {
return "用户不存在";
}
// 2. 校验密码
boolean isPasswordMatch = passwordEncoder.matches(password, user.getPassword());
if (!isPasswordMatch) {
return "密码错误";
}
// 3. 登录成功,后续可以生成JWT令牌或者创建Session
return "登录成功";
}
}
四、注意事项
- 输入验证不仅要做前端校验,后端必须再做一次校验,避免前端校验被绕过
- 密码加密不要使用自己实现的哈希算法,优先使用成熟的加密库
- 用户密码不要参与日志打印,避免日志泄露密码信息
- 登录接口建议添加限流逻辑,防止暴力破解密码