在应用开发中,密码安全是用户隐私保护的核心环节。直接存储明文密码存在极大的安全风险(如数据库泄露导致用户信息被盗)。MD5 加密作为一种广泛使用的哈希算法,可将密码转换为固定长度的字符串,但其本身存在局限性(如易受彩虹表攻击)。本文将结合 Spring Boot 演示如何实现 MD5 密码加密存储与验证,并探讨如何增强其安全性。
3.1 环境准备
3.2 创建项目
通过 Spring Initializr 创建项目,勾选:
3.3 添加依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>com.h2databasegroupId>
<artifactId>h2artifactId>
<scope>runtimescope>
dependency>
4.1 创建 MD5 工具类
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Md5Utils {
public static String encrypt(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(input.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
throw new RuntimeException("MD5加密失败", e);
}
}
}
代码解释:
MessageDigest
提供 MD5 算法实例。digest()
方法生成二进制哈希值。4.2 在注册逻辑中调用
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User register(String username, String password) {
String encryptedPwd = Md5Utils.encrypt(password);
User user = new User(username, encryptedPwd);
return userRepository.save(user);
}
}
5.1 用户实体类
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
private String password;
// 构造方法、Getter/Setter省略
}
5.2 数据库表结构(H2)
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(255) UNIQUE,
password VARCHAR(32) -- MD5哈希固定32位
);
6.1 登录验证逻辑
@Service
public class UserService {
public boolean login(String username, String password) {
User user = userRepository.findByUsername(username);
if (user == null) return false;
String encryptedInput = Md5Utils.encrypt(password);
return encryptedInput.equals(user.getPassword());
}
}
6.2 登录接口
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginRequest request) {
boolean success = userService.login(request.getUsername(), request.getPassword());
return success ? ResponseEntity.ok("登录成功") : ResponseEntity.status(401).body("用户名或密码错误");
}
}
7.1 加盐(Salt)技术
public class Md5Utils {
public static String encryptWithSalt(String input, String salt) {
return encrypt(input + salt);
}
}
// 注册时生成随机盐值并存储
public class User {
private String salt; // 新增字段
private String password;
public User(String username, String password) {
this.salt = UUID.randomUUID().toString();
this.password = Md5Utils.encryptWithSalt(password, this.salt);
}
}
7.2 使用 BCrypt 替代 MD5
// Spring Security 提供BCryptPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 加密
String encodedPwd = passwordEncoder.encode("123456");
// 验证
boolean matches = passwordEncoder.matches("123456", encodedPwd);
8.1 单元测试
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void testRegisterAndLogin() {
User user = userService.register("alice", "123456");
assertTrue(userService.login("alice", "123456"));
assertFalse(userService.login("alice", "wrong"));
}
}
8.2 检查数据库
ID | USERNAME | PASSWORD | SALT
1 | alice | e10adc3949ba59abbe56e057f20f883e | null -- 未加盐
1 | alice | 7b2b9b5d5c8d1e0f8a9d8c7b6a5e4d3f | 6d8a... -- 加盐后
9.1 配置自定义密码加密
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(username -> userService.loadUserByUsername(username))
.passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
return Md5Utils.encrypt(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(encode(rawPassword));
}
});
}
}
问题1:MD5加密结果不一致
input.getBytes("UTF-8")
。问题2:如何存储盐值?
问题3:升级加密算法
总结:
未来趋势:
学习资源: