首页/新闻资讯/正文详情

神龙大学习-BCrypt

发布时间:2026/9/23 7:25:22 来源:云帆数科 栏目:资讯中心
神龙大学习-BCrypt
Spring Boot 项目使用 BCrypt 实现密码加密与登录校验 本文基于“卡码网盘”项目的真实代码记录 BCrypt 的依赖引入、Bean 配置、变量注入、注册密码加密以及登录密码校验过程。## 一、为什么密码不能直接保存用户注册时提交的是原始密码。如果直接将原始密码保存到 MySQL一旦数据库泄露所有用户的密码都会直接暴露。本项目采用 BCrypt 对密码进行单向哈希textVue 前端提交原始密码生产环境必须使用 HTTPS↓Spring Boot 后端接收密码↓BCryptPasswordEncoder.encode() 生成哈希↓MySQL 只保存 password_hash登录时不会解密 BCrypt 哈希而是使用 matches() 判断用户输入的密码是否与数据库哈希匹配。BCrypt 自带随机盐因此同一个密码每次调用 encode() 得到的结果通常不同。这是正常现象不能用字符串相等来判断密码。## 二、本项目使用的技术环境- Java 21- Spring Boot 3.5.5- Maven- Spring Security Crypto- Spring Data JPA- MySQL项目中的相关文件textbackend/pom.xmlbackend/src/main/java/com/networkdisk/config/PasswordConfig.javabackend/src/main/java/com/networkdisk/auth/AuthService.javabackend/src/main/java/com/networkdisk/auth/User.java## 三、在 Maven 中导入 BCrypt 依赖打开后端项目的 pom.xml在 dependencies 中加入xmldependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-crypto/artifactId/dependency本项目使用 Spring Boot 父工程管理依赖版本xmlparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion3.5.5/versionrelativePath//parent因此 spring-security-crypto 一般不需要手动填写 versionSpring Boot 会提供相互兼容的版本。添加依赖后可以在项目根目录执行bashmvn test如果 IDE 仍提示找不到类需要重新加载 Maven 项目。需要注意这里只引入了密码加密模块并没有引入完整的 Spring Security Web 登录体系。## 四、在 Java 文件中导入 BCrypt 类依赖下载完成后在需要声明 BCrypt Bean 或使用 BCrypt 的 Java 类中加入javaimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;“Maven 导入依赖”和“Java 导入类”是两个不同步骤- pom.xml 的 dependency 让项目获得 BCrypt 类库。- Java 文件中的 import 让当前源文件可以直接使用 BCryptPasswordEncoder 类名。## 五、把 BCrypt 配置成 Spring Bean本项目没有在每次注册时手动 new BCryptPasswordEncoder()而是在配置类中统一创建对象。文件PasswordConfig.javajavapackage com.networkdisk.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;Configurationpublic class PasswordConfig {Beanpublic BCryptPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}这里的两个注解含义如下- Configuration告诉 Spring 这是一个配置类。- Bean把方法返回的 BCryptPasswordEncoder 对象交给 Spring 容器管理。应用启动后Spring 容器中就会存在一个 BCryptPasswordEncoder Bean其他业务类可以直接注入。### 是否需要设置强度参数默认写法是javanew BCryptPasswordEncoder()也可以显式指定强度例如javanew BCryptPasswordEncoder(12)强度越高计算成本越高抵抗暴力破解的能力也越强但注册和登录会更慢。没有经过压测时使用默认值通常更稳妥。## 六、在 Service 中引入 BCrypt 变量本项目采用构造器注入这是 Spring 官方实践中常见且便于测试的方式。首先在 AuthService 中声明成员变量javaprivate final BCryptPasswordEncoder passwordEncoder;然后把它写进构造方法参数并赋值给成员变量javapublic AuthService(UserRepository userRepository,BCryptPasswordEncoder passwordEncoder,TokenService tokenService) {this.userRepository userRepository;this.passwordEncoder passwordEncoder;this.tokenService tokenService;}完整的关键结构如下javaimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.stereotype.Service;Servicepublic class AuthService {private final BCryptPasswordEncoder passwordEncoder;public AuthService(BCryptPasswordEncoder passwordEncoder) {this.passwordEncoder passwordEncoder;}}因为 PasswordConfig 已经通过 Bean 注册了该类型的对象所以 Spring 创建 AuthService 时会自动找到并传入这个 Bean。当前类只有一个构造方法因此不需要再写 Autowired。不建议写成可变的字段注入javaAutowiredprivate BCryptPasswordEncoder passwordEncoder;构造器注入可以让依赖关系更清晰字段还可以声明为 final单元测试时也更容易传入 Mock 对象。## 七、注册时使用 encode() 加密密码卡码网盘项目的注册逻辑位于 AuthService.register()。核心代码javaUser user userRepository.save(new User(email,nickName,passwordEncoder.encode(request.password())));其中javarequest.password()是前端提交的原始密码javapasswordEncoder.encode(request.password())会生成 BCrypt 哈希。传给 User 实体和数据库的只有哈希值不是原始密码。为了让流程更容易理解也可以拆成变量javaString rawPassword request.password();String passwordHash passwordEncoder.encode(rawPassword);User user new User(email, nickName, passwordHash);userRepository.save(user);注意不要输出以下日志javaSystem.out.println(rawPassword);原始密码不应写入数据库、日志、接口响应或异常信息。## 八、数据库只保存 BCrypt 哈希本项目的 User 实体使用 password_hash 字段保存密码哈希javaColumn(name password_hash, nullable false, length 100)private String passwordHash;推荐的 MySQL 字段定义为sqlpassword_hash VARCHAR(100) NOT NULLBCrypt 哈希通常以 $2a$、$2b$ 等开头。字段长度不要按用户原始密码长度设置预留 100 个字符比较合适。实体构造方法接收的参数也应明确命名为 passwordHashjavapublic User(String email, String nickName, String passwordHash) {this.email email;this.nickName nickName;this.passwordHash passwordHash;this.createdAt LocalDateTime.now();}不要给接口响应对象添加 passwordHash 字段。即使哈希不是明文也不应该返回给前端。## 九、登录时使用 matches() 校验密码本项目要求使用用户名登录不使用邮箱登录。后端先按用户名查询用户再校验密码javaTransactional(readOnly true)public LoginResponse login(LoginRequest request) {String username request.username().trim();User user userRepository.findByNickName(username).orElseThrow(AuthService::loginFailed);if (!passwordEncoder.matches(request.password(),user.getPasswordHash())) {throw loginFailed();}return new LoginResponse(tokenService.create(user.getId()),user.getId(),user.getNickName());}matches() 的参数顺序非常重要javapasswordEncoder.matches(用户输入的原始密码, 数据库中的BCrypt哈希)即javaboolean matched passwordEncoder.matches(rawPassword, passwordHash);不能在登录时重新调用 encode() 后比较字符串java// 错误示例passwordEncoder.encode(rawPassword).equals(passwordHash)因为 BCrypt 每次加密都会使用随机盐即使原始密码相同生成的哈希也通常不同。## 十、随机加盐后登录时为什么还能校验成功这是使用 BCrypt 时最容易产生疑问的地方注册和登录时如果使用了不同的随机盐生成的哈希确实会不同那么系统是怎样判断密码正确的关键点是**登录时不会重新调用 encode() 生成一个新哈希再与数据库字符串直接比较而是调用 matches()。**注册时执行javaString passwordHash passwordEncoder.encode(123456);数据库中保存的完整 BCrypt 字符串类似text$2a$10$abcdefghijklmnopqrstuu1VnV3fTn0exampleHashValue这个字符串不只是单纯的哈希结果其中还包含text算法版本 计算强度 注册时生成的盐值 密码哈希结果例如text$2a$ → BCrypt 算法版本10$ → 计算强度 cost后续内容 → 盐值和哈希结果因此盐值不需要另外创建数据库字段。只要完整保存 encode() 返回的 BCrypt 字符串注册时使用的盐值就不会丢失。登录时本项目执行javapasswordEncoder.matches(request.password(),user.getPasswordHash())matches() 内部会完成以下过程text读取数据库中的完整 BCrypt 字符串↓解析出注册时使用的算法版本、计算强度和盐值↓使用解析出的原盐值计算本次输入的密码↓将计算结果与数据库中的哈希结果进行安全比较↓返回 true 或 false所以登录校验使用的是数据库哈希中记录的原盐值并不会为本次比较生成一个新盐。下面这段代码可以直接验证javaBCryptPasswordEncoder encoder new BCryptPasswordEncoder();String hash1 encoder.encode(123456);String hash2 encoder.encode(123456);System.out.println(hash1);System.out.println(hash2);// 两次 encode() 使用不同的随机盐所以结果通常不相等。System.out.println(hash1.equals(hash2));// false// matches() 会读取 hash1 中原有的盐值因此可以正确校验。System.out.println(encoder.matches(123456, hash1));// true// hash2 使用另一份随机盐但同样可以独立完成校验。System.out.println(encoder.matches(123456, hash2));// true// 输入错误密码时校验失败。System.out.println(encoder.matches(654321, hash1));// false错误的登录校验方式是java// 错误encode() 会生成新的随机盐结果通常不会等于数据库哈希。boolean matched passwordEncoder.encode(request.password()).equals(user.getPasswordHash());正确方式是java// 正确从数据库哈希中读取注册时的盐值并完成校验。boolean matched passwordEncoder.matches(request.password(),user.getPasswordHash());随机盐的价值在于即使两个用户设置了完全相同的密码他们保存在数据库中的 BCrypt 字符串通常也不相同。攻击者不能只通过观察相同的哈希就判断两个用户使用了相同密码。## 十一、为什么用户名不存在和密码错误返回同一句话本项目统一返回javaprivate static AuthBusinessException loginFailed() {return new AuthBusinessException(LOGIN_FAILED, 用户名或密码错误);}不要分别提示“用户名不存在”和“密码错误”否则攻击者可以通过接口判断某个用户名是否已经注册。## 十二、前端需要使用 BCrypt 吗不需要。BCrypt 放在 Spring Boot 后端Vue 前端只负责收集密码并通过 HTTPS 提交javascriptawait login({username: username.value,password: password.value})前端不应该- 保存原始密码- 把密码写进 localStorage- 保存数据库中的 BCrypt 哈希- 把前端哈希当作 HTTPS 的替代品。在普通的用户名密码登录场景中传输安全由 HTTPS 负责密码存储安全由后端 BCrypt 负责。## 十三、如何编写单元测试可以使用真实的 BCryptPasswordEncoder 验证注册密码是否被正确哈希javaBCryptPasswordEncoder passwordEncoder new BCryptPasswordEncoder();String rawPassword 123456;String passwordHash passwordEncoder.encode(rawPassword);assertThat(passwordHash).isNotEqualTo(rawPassword);assertThat(passwordEncoder.matches(rawPassword, passwordHash)).isTrue();assertThat(passwordEncoder.matches(wrong-password, passwordHash)).isFalse();本项目中的测试还会检查- 注册后保存的不是原始密码- 正确密码可以登录- 错误密码不能登录- 用户名不存在时不能签发令牌。执行测试bashcd backendmvn test## 十四、常见错误总结### 1. 找不到 BCryptPasswordEncoder检查 pom.xml 是否加入xmlartifactIdspring-security-crypto/artifactId并检查 Java 文件是否加入javaimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;### 2. Spring 提示找不到 BCryptPasswordEncoder Bean检查是否存在javaConfigurationpublic class PasswordConfig {Beanpublic BCryptPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}配置类还必须位于 Spring Boot 启动类能够扫描到的包路径下。本项目启动类位于 com.networkdisk配置类位于 com.networkdisk.config因此可以被自动扫描。### 3. 同一个密码加密结果不一样这是 BCrypt 随机盐的正常效果。请使用 matches()不要直接比较两个哈希字符串。### 4. 登录一直提示密码错误检查参数顺序是否正确javamatches(rawPassword, passwordHash)并确认数据库保存的是 encode() 的返回值而不是原始密码或被重复加密的字符串。### 5. 把 BCrypt 放在前端不建议。前端代码和数据都可以被用户查看而且前端哈希无法代替 HTTPS。BCrypt 应由可信的后端执行。## 十五、完整流程总结text注册前端原始密码→ RegisterRequest→ passwordEncoder.encode(rawPassword)→ User.passwordHash→ MySQL password_hash登录前端用户名和原始密码→ 按用户名查询 User→ passwordEncoder.matches(rawPassword, passwordHash)→ 成功后签发登录令牌本项目使用 BCrypt 的关键只有两句javaString passwordHash passwordEncoder.encode(rawPassword);boolean matched passwordEncoder.matches(rawPassword, passwordHash);真正需要注意的是BCrypt 必须放在后端、数据库只保存哈希、登录必须使用 matches()、生产环境必须启用 HTTPS并且任何接口都不能返回密码哈希。

相关推荐

光储一体化系统设计与实践指南
光储一体化系统设计与实践指南

1. 光储一体化系统初探去年夏天帮朋友改造他位于郊区的民宿时,我第一次完整实践了光储一体化方案。当光伏板在烈日下源源不断产生电能,储能系统将多余电力储存起来,傍晚时分整个院落的灯光自然亮起时,那种自给自足的满足感&#x… · 2026/9/23 7:25:09

Claude Code知识工作插件实战:从slash commands到工作流自动化
Claude Code知识工作插件实战:从slash commands到工作流自动化

1. 从"knowledge-work-plugins"这个名字说起:它到底在解决什么问题第一次看到knowledge-work-plugins这个仓库名,很多人会下意识地把它归类成"又一个插件合集"。但如果你真的在 Claude Code 或 Claude Cowork 里干过几天活&#xff… · 2026/9/23 7:25:09

ABAQUS模拟卷尺正反向弯曲的工程实践
ABAQUS模拟卷尺正反向弯曲的工程实践

1. 项目背景与工程意义卷尺作为日常测量工具,其金属薄壳的弯曲特性直接影响着使用手感和耐久性。在ABAQUS中模拟这种正反向弯曲行为,能够帮助工程师理解材料在循环载荷下的力学响应。我最近完成的一个工业咨询项目恰好涉及这个问题——某品牌卷尺在使用三… · 2026/9/23 7:25:09

基于LSTM的电力负荷时间序列预测:从数据清洗到多步预测完整实践
基于LSTM的电力负荷时间序列预测:从数据清洗到多步预测完整实践

简介:这是一份基于深度学习算法实现电力负荷时间序列未来预测的 Python 源码项目,围绕负荷历史数据完成特征构造、模型训练与结果评估,覆盖 LSTM、GRU、Transformer、ARIMA、随机森林、决策树、KNN 等多种算法,适合计科、人工智能… · 2026/9/23 8:13:40

从Keil5迁移到VSCode+GCC:GD32开发环境搭建与实战指南
从Keil5迁移到VSCode+GCC:GD32开发环境搭建与实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/23 8:13:40

用Office文档搭建企业AI知识库:从RAG原理到Dify实操指南
用Office文档搭建企业AI知识库:从RAG原理到Dify实操指南

这两年AI大模型火得一塌糊涂,几乎每个企业都在琢磨怎么把AI真正用起来。可我接触了这么多客户和同行,发现大家碰到的第一个瓶颈往往不是模型不够聪明,而是企业自己的数据根本喂不进去。很多公司的核心经验、流程、制度、技术文档,… · 2026/9/23 8:13:40

obsidian-livesync 仓库的 AI 编码助手规范:读懂 AGENTS.md 中的协作、风格与发布纪律
obsidian-livesync 仓库的 AI 编码助手规范:读懂 AGENTS.md 中的协作、风格与发布纪律

数据同步 【免费下载链接】obsidian-livesync 项目地址: https://gitcode.com/gh_mirrors/ob/obsidian-livesync 点击查看 免费下载 Self-hosted LiveSync(obsidian-livesync)是一个用于跨设备同步 Obsidian 库的插件,代码库采用… · 2026/9/23 8:13:34

智能学术专著创作平台:提升效率与质量的新范式
智能学术专著创作平台:提升效率与质量的新范式

1. 项目概述:学术专著创作的新范式去年协助一位教授完成学科评估材料时,我亲眼见证了一部学术专著从构思到出版的完整历程。这位学者花费了整整八个月时间,每天工作到凌晨两点,最终交稿时体重下降了12斤。这种"学术苦修"… · 2026/9/23 8:13:34

dnf元素觉醒叫什么新手避坑
dnf元素觉醒叫什么新手避坑

5个坑让你DNF元素觉醒从入门到精通 刚接触DNF元素觉醒的玩家,是不是也遇到过这种尴尬:看着攻略把技能点加满了,结果进图一放火球,伤害低得可怜;或者明明照着视频操作,觉醒技能却放不出来,卡在原地干着急。这种“学会了操作逻辑,却不知道怎么在… · 2026/9/23 8:13:34

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码