前言

这是我做的第一个项目。前几天我对安全的理解只停留在“别写出 bug 就行”。直到在测试数据库时,亲眼看到 user_password 字段里躺着一串串明文密码,才想起之前刷到的视频里一句话:“明文密码 = 红线”。本文记录我从明文存储到使用 BCrypt 的全过程,留给也在苦苦写项目的同学做参考。

问题爆发:数据库里的“裸奔”密码

项目初期我只想先跑通流程,直接把前端传来的密码丢进数据库。控制层代码大概是这样:

1
2
3
4
5
6
7
8
9
// 错误示范,请勿模仿
@PostMapping("/register")
public Result register(@RequestBody User user) {
if (user.getPassword().length() < 8) {
return Result.error("密码长度不足");
}
userMapper.insert(user); // 直接存明文
return Result.success();
}

当我用 Navicat 打开数据表,"12345678"、"abcdefg" 等弱密码全在里面。此刻我才意识到:如果数据库被拖库,所有用户账号都会暴露。

解决过程:BCrypt 的救赎

1. 选型

查资料后锁定几个常见方案:

  • MD5:不可逆,但彩虹表和 GPU 暴力破解太快;
  • SHA:同样属于快速哈希,对密码保护不够;
  • BCrypt:自带盐值,计算慢,可拖慢暴力破解。

结合 Spring 生态,我直接使用 BCryptPasswordEncoder,不必自己维护盐值,整合也很顺。

2. 代码改造

引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

注册加密 Bean

1
2
3
4
5
6
7
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

改造注册 / 登录逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class UserServiceImpl implements UserService {

@Autowired
private PasswordEncoder passwordEncoder;

@Override
public void register(User user) {
String encodedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
userMapper.insert(user);
}

@Override
public User login(String account, String password) {
User user = userMapper.selectByAccount(account);
if (user == null) {
throw new BusinessException("用户不存在");
}
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BusinessException("密码错误");
}
return user;
}
}

3. 新坑:字段长度不够

BCrypt 生成的哈希大约 60 个字符,而我的 user_password 是 varchar(50)。插入时报错,只能赶紧迁表:

1
2
ALTER TABLE user
MODIFY COLUMN user_password VARCHAR(60) NOT NULL COMMENT '密码哈希';

总结

这次踩坑的收获:

  • 明文密码绝对不能入库,BCrypt 是上手友好的方案;
  • 哈希字符串会更长,库表字段要预留空间;
  • 善用框架提供的工具,别重复造轮子。

下一篇我应该还会记录在这个项目遇到的其他问题,希望还能坚持写下去,欢迎催更!


更新 – 相关文章

这个用户中心项目系列的其他文章: