【SpringBoot】MD5加盐算法的详解
目录
一、什么是加盐算法
二、如何实现加盐算法
2.1 加盐算法代码实现
2.2 注册页面中进行密码加盐
2.3 登录页面进行加盐的解密
2.4 注册和登录
一、什么是加盐算法
加盐算法是一种用于增强密码安全性的技术。这种技术通过在密码存储过程中添加一个随机生成的盐值(salt),来增加密码被破解的难度。举个例子:
在日常生活中,做菜是生活中的基本操作。炒青菜餐桌中必备的一道菜,不同的人炒青菜放盐的程度是不同的,换句话来说不同的人炒同一种菜放盐的多与少是一种随机的操作。因此,在注册账号时,不同用户在注册时难免会使用相同的密码。加盐算法会根据不同的用户生成一串随机的字符串与用户所输入的密码进行结合生成一个最终的结果值,而这个最终值得到的就是加密后的密码。
具体来说,加盐加密的过程如下:
- 生成盐值:后端在存储一个密码时,首先会随机生成一个盐值。这个盐值是一个随机字符串,用于增加密码的复杂性。
- 结合盐值和密码:将生成的盐值和用户输入的密码进行有规则的结合,通常是将盐值附加在密码的前面或后面,或者通过其他方式进行组合。
- 加密处理:将结合后的数据使用加密算法进行加密。常见的加密算法包括MD5、SHA-256等。在Spring Security中,还可以使用更强大的加密方式,如BCrypt。本期讲解使用MD5。
- 存储加密后的数据和盐值:将加密后的数据和盐值按照一定规则组合起来,并存储在数据库中。通常,盐值会被保存在与加密密码相同的记录中,以便在验证用户密码时使用。
二、如何实现加盐算法
2.1 加盐算法代码实现
首先,我们要了解到:
生成一个随机盐值可使用 UUID ,UUID 是一个128比特的数值,通常由 32 个 16 进制数字组成,并以连字号分为五段,例如:550e8400-e29b-41d4-a716-446655440000,因此可使用 replace 方法去除 - 。
生成一个 MD5 散列值,可使用 DigestUtils类 中的 .md5DigestAsHex 方法将生成的随机盐值 salt 和 password 结合,并通过 .getBytes 将结合后的字符串转换成一个字节数组即 .getBytes(StandardCharsets.UTF_8) 。
此外需要注意的是:
- MD5(Message-Digest Algorithm 5)是一种广泛使用的散列函数,可以产生一个128位(16字节)的散列值,通常表示为32位的十六进制数。
- md5DigestAsHex 是 DigestUtils 类中的一个静态方法。这个方法接受一个字节数组作为输入,计算其MD5散列值,并将结果转换为十六进制字符串。
- StandardCharsets.UTF_8 是一个表示UTF-8字符集的常量,它指定了字符串到字节数组的编码方式。
代码实现:
/*** 加密工具类*/
public class PasswordUtils {public static String encrypt(String password){// 1.盐值String salt = UUID.randomUUID().toString().replace("-","");// 2.将盐值+密码进行 md5 得到最终密码String finalPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes(StandardCharsets.UTF_8));// 3.将盐值和最终密码返回return salt+"$"+finalPassword;}/*** 加盐验证* @param password 待验证的密码* @param dbPassword 数据库中的密码:盐值$最终密码* @return*/public static boolean decrypt(String password,String dbPassword) {if(!StringUtils.hasLength(password) || !StringUtils.hasLength(dbPassword)|| dbPassword.length() != 65) {return false;}// 1.得到盐值String[] dbPasswordArray = dbPassword.split("\\$") ;if (dbPasswordArray.length != 2) {return false;}//盐值String salt = dbPasswordArray[0];//最终正确密码String dbFinalPassword = dbPasswordArray[1];// 2.加密待验证的密码String finalPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes(StandardCharsets.UTF_8));// 3.对比if (finalPassword.equals(dbFinalPassword)) {return true;}return false;}public static void main(String[] args) {// 得到盐值和最终密码一共65位System.out.println(encrypt("111"));}
}
在该类中生成一个 main 方法,测试最后生成的字符串符不符合预期,输出结果:

将上述的字符串,定义为一个字符串,验证是否加密成功。
public static void main(String[] args) {
/* // 得到盐值和最终密码一共65位System.out.println(encrypt("111"));*/String dbPassword = "0f0d62e88c6c41dc83163e2813147111$b7321a14e1e5f3360a0d0d59e2b3cd6e";System.out.println("当密码为123时:"+decrypt("123",dbPassword));System.out.println("当密码为111时:"+decrypt("111",dbPassword));}

2.2 注册页面中进行密码加盐
/*** 注册* @param userinfo* @return*/@RequestMapping("/reg")public ResultAjax reg(Userinfo userinfo) {// 1.校验参数if (userinfo == null || !StringUtils.hasLength(userinfo.getUsername()) ||!StringUtils.hasLength(userinfo.getPassword())) {return ResultAjax.fail(-1,"异常");}// 实现密码加盐userinfo.setPassword(PasswordUtils.encrypt(userinfo.getPassword()));// 2.请求接口 service 进行添加接口int ret = userService.reg(userinfo);// 3.将结果返回给前端return ResultAjax.success(ret);}
2.3 登录页面进行加盐的解密
/*** 登录* @param userinfoVO* @return*/@RequestMapping("/login")public ResultAjax login(UserinfoVO userinfoVO, HttpServletRequest request) {// 1.参数校验if (userinfoVO == null || !StringUtils.hasLength(userinfoVO.getUsername())|| !StringUtils.hasLength(userinfoVO.getPassword())) {// 非法登录return ResultAjax.fail(-1,"非法登录!");}// 2.根据用户名查询对象,判断用户名是否错误Userinfo userinfo = userService.getUserByName(userinfoVO.getUsername());if (userinfo == null && userinfo.getId() == 0) {return ResultAjax.fail(-2,"账号或密码错误!");}// 3.使用对象中的密码和输入的密码进行对比,判断密码是否错误// 加盐解密if (!PasswordUtils.decrypt(userinfoVO.getPassword(),userinfo.getPassword())) {return ResultAjax.fail(-2,"账号或密码错误!");}// 4.成功后将对象存储到 session 中HttpSession session = request.getSession();session.setAttribute(ApplicationVariable.SESSION_USERINFO_KEY,userinfo);// 5.结果返回给用户return ResultAjax.success(1);}
此时查询数据库中,只有一条数据,下面再注册一个用户,观察是否生成密码。

2.4 注册和登录
注册页面
输入用户名 lisi 和密码 123 后,提示注册成功。

在数据中查询对应的数据,验证密码 123 被加秘为一大串字符串。

再来到登录页面,输入用户名 lisi 和密码 123,提示登陆成功。

跳转到个人页面。

注册页面和登录页面的讲解和源码在前两篇博客中已有详细的讲解,感兴趣可以去看看。
本篇博客到这里就结束了,感谢各位的观看。
相关文章:
【SpringBoot】MD5加盐算法的详解
目录 一、什么是加盐算法 二、如何实现加盐算法 2.1 加盐算法代码实现 2.2 注册页面中进行密码加盐 2.3 登录页面进行加盐的解密 2.4 注册和登录 一、什么是加盐算法 加盐算法是一种用于增强密码安全性的技术。这种技术通过在密码存储过程中添加一个随机生成的盐值&…...
kotlin与MVVM结合使用总结(一)
一、Kotlin 与 MVVM 结合的核心优势 代码简洁性 数据类(data class)简化 Model 层定义,自动生成equals/hashCode/toString扩展函数简化 View 层逻辑(如点击事件扩展)lateinit/by lazy优化 ViewModel 属性初始化 异步处…...
累计完工数量达到了xxxx超过了最大可完工数量xxxx
之前解决过一次,没有记录下来,不记得发生什么事情。又浪费几个小时去分析问题。这次的经历有点痛苦,碰上多表关连数据的勾稽。分析是河南用户的非法操作造成的。没有领料记录入不了库,跨月了。财务要求删单处理。删单之后…...
飞鸟与鱼不同路
看,好美的太阳。 正是因为有人看才会觉得美,若无人问津,美又从何而来。 嘿嘿,今天提出辞去综合教研室主任一职,不想在这个管理上废时间啦~ 把时间用来考试.........用来做自己的事情,花在自己的身上&…...
若依RuoYi-Cloud-Plus微服务版(完整版)前后端部署
一.目标 在浏览器上成功登录进入 二.源码下载 后端源码:前往Gitee下载页面(https://gitee.com/dromara/RuoYi-Cloud-Plus)下载解压到工作目录。 前端源码: 前往Gitee下载页面(https://gitee.com/JavaLionLi/plus-ui)下载解压到工作目录。 文档地址&a…...
【redis】list类型:基本命令(下)
文章目录 LLENLREMLTRIMLSET阻塞版本命令BLPOP 和 BRPOP区别使用方式 命令小结内部编码 LLEN 获取 list 的长度 语法: LLEN key时间复杂度: O ( 1 ) O(1) O(1)返回值: list 长度 LREM 删除 count 个 key 中的元素 语法: LREM…...
【数据挖掘】知识蒸馏(Knowledge Distillation, KD)
1. 概念 知识蒸馏(Knowledge Distillation, KD)是一种模型压缩和知识迁移技术,旨在将大型复杂模型(称为教师模型)中的知识传递给一个较小的模型(称为学生模型),以减少计算成本&…...
VSCode 搭建C++编程环境 2025新版图文安装教程(100%搭建成功,VSCode安装+C++环境搭建+运行测试+背景图设置)
名人说:博观而约取,厚积而薄发。——苏轼《稼说送张琥》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、VScode下载及安装二、安装 MinGW-w64 工具链三、Windows环境变量配置四、检查 M…...
Ubuntu24.04 LTS 版本 Linux 系统在线和离线安装 Docker 和 Docker compose
一、更换软件源并更新系统 在 Ubuntu 24.04 LTS 中,系统引入了全新的软件源配置格式。现在的源配置文件内容更加结构化且清晰,主要包含了软件类型 (Types)、源地址 (URIs)、版本代号 (Suites) 以及组件 (Components) 等信息。 # cat /etc/apt/sources.li…...
从 pip 到 Poetry:开启高效 Python 包管理新时代
目录 从 pip 到 Poetry:开启高效 Python 包管理新时代 一、pip 与 Poetry 的基本区别 二、Poetry 相对于 pip 的优势 1. 依赖管理与版本锁定 2. 内置虚拟环境管理 3. 统一的项目管理流程 4. 精细的依赖解析器 5. 更友好的 CLI 工具 三、如何快速上手 Poetry…...
MTK Android12 最近历史任务 最左侧的清除历史任务改到页面底部
Android最近历史任务页面 -清除所有- 功能按钮放到底部 文章目录 需求需求原因 修改的核心文件实现方案最近历史任务基本UI结构了解代码实现思路实现方案RecentsViewTaskOverlayFactory在overview_actions_containerOverviewActionsView 实际效果 总结 需求 最近历史任务重&am…...
TCP协议支持全双工原因TCP发送接收数据是生产者消费者模型
一、TCP支持全双工的原因 TCP协议支持全双工,即使用TCP协议进行通信时,服务端和客户端可以同时进行数据的发送和接收,互不干扰,实现同时双向传输数据。 这是因为使用TCP协议通信时,读写套接字的文件描述符既用来发送…...
文件操作2
7. ⽂件读取结束的判定 7.1 被错误使用的 feof 牢记:在文件读取过程中,不能用 feof 函数的返回值直接来判断文件的是否结束。 feof 的作用是:当文件读取结束的时候,判断读取结束的原因是否是:遇到文件尾结束。 1. …...
linux中yum和wget指令的区别
yum 和 wget 都是 Linux 上的下载工具,但它们的用途、下载方式和适用场景不同。以下是它们的 主要区别: 1. yum 是软件包管理器,wget 是文件下载工具 功能yumwget用途安装、更新和管理 RPM 软件包从 HTTP/HTTPS/FTP 下载文件工作方式通过 yu…...
《又是二叉树?递归与回溯的经典应用》
“ 我喜欢晴天,你恰好是最好的太阳” 226.翻转二叉树 力扣题目链接(opens new window) 翻转一棵二叉树。 这道题我们可以通过递归法解决,我们只要递归的把每一个节点的左右孩子反转一下就能解决了。 代码如下: var invertTree function(ro…...
Qt/C++音视频开发82-系统音量值获取和设置/音量大小/静音
一、前言 在音视频开发中,音量的控制分两块,一个是控制播放器本身的音量,绝大部分场景都是需要控制这个,这个不会影响系统音量的设置。还有一种场景是需要控制系统的音量,因为播放器本身的音量是在系统音量的基础上控…...
从零到精通文本指令:打造个人AI助理的完整指令库(Prompt 指令实操)
文章目录 从零到精通文本指令:打造个人AI助理的完整指令库(Prompt 指令实操)创作指令创作指令**润色指令****扩写指令** 问答指令直接问答材料问答时间逻辑问答 总结、摘要、翻译指令总结信息抽取翻译 从零到精通文本指令:打造个人AI助理的完整指令库(Pr…...
C# NX二次开发:获取模型中所有的草图并获取草图中的对象
大家好,今天接着讲NX二次开发获取草图相关。 获取草图的方法是从workPart中获取,如下面的例子所示: List<Tag> tags new List<Tag>(); SketchCollection sketchCollection workPart.Sketches; …...
基于SpringBoot和MybatisPlus实现通用Controller
基于SpringBoot和MybatisPlus实现通用Controller,只需要创建实体类和mapper接口,单表增删改查接口就已经实现,提升开发效率 1.定义通用controller package com.xian.controller;import cn.hutool.core.map.MapUtil; import com.baomidou.my…...
锤头线和倒锤头线
1、锤头线 是指一根没有上影线或上影线很短,而下影线很长,实体却很小的K线。其K线实体可以是阴线或是阳线,类似于T字。 锤头线的特征有以下三点: 实体很小,下影线长度大于或等于实体的两倍。下影线越长时,如股价处于低位,则上涨的可能性越大。 如股价处于高位,则下跌…...
蓝桥杯嵌入式组第十二届省赛题目解析+STM32G431RBT6实现源码
文章目录 1.题目解析1.1 分而治之,藕断丝连1.2 模块化思维导图1.3 模块解析1.3.1 KEY模块1.3.2 LED模块1.3.3 LCD模块1.3.4 TIM模块1.3.5 UART模块1.3.5.1 uart数据解析 2.源码3.第十二届题目 前言:STM32G431RBT6实现嵌入式组第十二届题目解析源码&#…...
STM32上实现简化版的AUTOSAR DEM模块
文章目录 摘要摘要 在一些可以不使用AUTOSAR的项目中,往往也有故障检测和DTC存储的需求,开发一套类似于AUTOSAR DEM模块的软件代码,能够满足DTC的检出和存储,使用FalshDB代替Nvm模块,轻松构建持久化存储,如果你也有这样的需求,请阅读本篇,希望能够帮到你。 /*********…...
如何用终端运行一个SpringBoot项目
在项目开发阶段,为了能够快速测试一个SpringBoot项目的执行结果,就可以采用终端(黑窗)运行查看,因为我们不能要求每一个客户都安装idea并且适配我们的项目版本。 下面将展示打包运行这两个方面的过程: 创建…...
多线程与并发编程 面试专题
多线程与并发编程 面试专题 线程的基础概念基础概念线程的创建线程的状态线程的终止方式start 与 run 区别线程的常用方法 锁锁的分类深入synchronized深入ReentrantLock死锁问题 阻塞队列线程池 线程的基础概念 基础概念 进程与线程 进程:指运行中的程序。 比如我…...
米尔电子-LR3568-烧录鸿蒙
最近开始搞鸿蒙,用的是米尔的LR3568。 开贴记录。 首先要在LR3568上烧录鸿蒙 一、安装准备 1.从米尔电子上下载资料 网址:米尔开发者中心 注册完成后,进入页面,选择我的产品,添加PN和SN PN和SN可以在包装盒上找到 添加到这里…...
Redis Sentinel 及 Redisson 连接问题全解析
在 Kubernetes (k8s) 环境下使用 Redis Sentinel 进行高可用部署时,可能会遇到 failover 超时、Sentinel 误判、Spring Boot 连接失败 以及 Redisson 配置错误等问题。本文将对这些问题进行汇总分析,并提供详细的解决方案。 1️⃣ Redis Sentinel 介绍 …...
基于Flink SQL的实时指标多维分析模型
数据流程介绍 1.创建源表kafka接入消息队列数据,定义字段映射规则; 2.创建目标表es_sink配置Elasticsearch输出; 3.通过多级视图(tmp→tmp_dedup→tmp1/tmp2→tmp3→tmp_groupby)实现数据清洗、去重、状态计算&#x…...
算法刷题整理合集(一)
算法刷题整理合集(一) 本篇博客旨在记录自已的算法刷题练习成长,里面注有详细的代码注释以及和个人的思路想法,希望可以给同道之人些许帮助。本人也是算法小白,水平有限,如果文章中有什么错误或遗漏之处&am…...
C++ STL—— String库
在C编程中,字符串操作是几乎每个项目都会涉及的基础功能。C标准模板库(STL)中的string类为我们提供了强大而灵活的工具,使得字符串的处理变得简单高效。无论是字符串的创建、修改、查找,还是复杂的文本处理,…...
【从零开始学习计算机科学】数据库系统(二)关系数据库 与 关系代数
【从零开始学习计算机科学】数据库系统(二)关系数据库 与 关系代数 关系数据库结构化查询语言SQL数据定义语言(DDL)数据查询语言(Data Query Language, DQL)数据操纵语言(Data Manipulation Language, DML)数据控制语言(Data Control Language, DCL)关系型数据库的优…...
