Spring?后端安全雙劍之JWT?無狀態(tài)認(rèn)證?+?密碼加鹽加密實(shí)戰(zhàn)指南
引言
在 Java Spring 后端開發(fā)中,“安全” 永遠(yuǎn)是繞不開的話題,用戶登錄如何免 Session 認(rèn)證?用戶密碼如何防止泄露?我們通過上篇令牌技術(shù)下篇加鹽/加密,這兩個(gè)核心技術(shù),來聊聊關(guān)于如何讓后端的認(rèn)證體系更安全、更可靠
加密/加鹽
加密的必要性
在 MySQL 數(shù)據(jù)庫的日常使用過程中,用戶密碼、身份證號碼、手機(jī)號碼這類敏感數(shù)據(jù)的加密處理至關(guān)重要,這是保障數(shù)據(jù)安全的核心環(huán)節(jié)
倘若直接以明文形式存儲這些信息,一旦數(shù)據(jù)庫遭遇黑客攻擊并被非法侵入,攻擊者就能輕易竊取到用戶的各類敏感信息。這種情況的發(fā)生,不僅會(huì)導(dǎo)致用戶個(gè)人信息泄露,還可能給用戶自身或所屬企業(yè)帶來財(cái)產(chǎn)方面的損失,造成難以估量的后果
密碼算法分類
密碼算法主要分為三類:對稱密碼算法,?對稱密碼算法,摘要算法

1. 對稱加密算法
- 核心特性:加密、解密使用同一密鑰,運(yùn)算速度快、效率高。
- 子類型:
- 分組密碼算法:將數(shù)據(jù)分割為固定長度的塊進(jìn)行加密(如 AES、DES)。
- 流密碼算法:逐字節(jié)生成密鑰流,與明文逐位加密(如 ChaCha)。
- 適用場景:大數(shù)據(jù)量加密(如文件、數(shù)據(jù)庫字段),需注意密鑰安全管理。
2. 非對稱加密算法
- 核心特性:使用公鑰 + 私鑰的密鑰對,公鑰可公開、私鑰需保密;公鑰加密的數(shù)據(jù)僅能通過對應(yīng)私鑰解密(反之亦然)。
- 優(yōu)勢:解決了對稱加密的 “密鑰分發(fā)” 問題,支持?jǐn)?shù)字簽名(私鑰簽名、公鑰驗(yàn)證)。
- 不足:加解密速度遠(yuǎn)慢于對稱加密。
- 適用場景:小數(shù)據(jù)加密(如密鑰交換、數(shù)字簽名),典型算法如 RSA、ECC。
3. 摘要算法
- 核心特性:單向哈希運(yùn)算,將任意長度數(shù)據(jù)轉(zhuǎn)換為固定長度的 “摘要值”;無法從摘要反推原文,數(shù)據(jù)微小變化會(huì)導(dǎo)致摘要完全不同。
- 子類型:
- MD(Message Digest):消息摘要算法(如 MD5)。
- SHA-1(Secure Hash Algorithm):安全散列算法。
- MAC(Message Authentication Code):消息認(rèn)證碼算法。
- 適用場景:數(shù)據(jù)完整性驗(yàn)證、密碼存儲(存摘要而非明文)、數(shù)字簽名組件。
加密思路
本篇文字主要以MD5算法來講解加密
MD5可加密原理:加鹽
MD5本身是不可逆的,加上相同的密碼經(jīng)過MD5哈希之后的密?是相同的,就很有可能被破解,但是我們可以采?為?個(gè)密碼拼接?個(gè)隨機(jī)字符來進(jìn)?加密,這個(gè)隨機(jī)字符我們稱之為 “鹽”,這樣子即使黑客拿到數(shù)據(jù)庫的數(shù)據(jù),也是加密后字符串和鹽的組合的字符串

密?相同,鹽值相同,則證明 明?相同
運(yùn)用加密/加鹽
寫加密/解密?具類
public class SecurityUtil {
//加密
//返回鹽值+md5(鹽值+明文)
public static String encrypt(String password) {
String salt = UUID.randomUUID().toString().replace("-", "");
String securityPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes(StandardCharsets.UTF_8));
return salt + securityPassword;
}
//解密
public static boolean verify(String inputPassword, String sqlPassword){
if(!StringUtils.hasLength(inputPassword)){
return false;
}
if(sqlPassword==null||sqlPassword.length()!=64){
return false;
}
String salt = sqlPassword.substring(0,32);
String securityPassword = DigestUtils.md5DigestAsHex((salt + inputPassword).getBytes(StandardCharsets.UTF_8));
return sqlPassword.equals(salt + securityPassword);
}
}工具解析:

使用加密/解密?具類簡單測試:
public static void main(String[] args) {
String encrypt = encrypt("123456");
boolean verify = verify("123456", encrypt);
System.out.println(verify);
}測試結(jié)果:

運(yùn)用實(shí)戰(zhàn)
創(chuàng)建響應(yīng)和請求實(shí)體類:
@Data
public class UserLoginRequest {
@NotNull(message = "用戶名不能為空")
private String userName;
@NotNull(message = "密碼不能為空")
private String password;
}
@Data
@AllArgsConstructor
public class UserLoginResponse {
private Integer userId;
private String token;
}UserLoginResponse 接口類:
@RequestMapping("/login")
public UserLoginResponse login(@RequestBody @Validated UserLoginRequest userLoginRequest){
log.info("用戶登錄,用戶名:{}",userLoginRequest.getUserName());
return userService.checkPassword(userLoginRequest);
}
UserServiceImpl Serviece類:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Resource(name = "blogServiceImpl")
private BlogService blogService;
@Override
public UserLoginResponse checkPassword(UserLoginRequest userLoginRequest) {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(UserInfo::getUserName,userLoginRequest.getUserName())
.eq(UserInfo::getDeleteFlag,0);
UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
if(userInfo==null){
//用戶不存在
throw new BlogException("用戶不存在");
}
//判斷密碼是否一致
if(!SecurityUtil.verify(userLoginRequest.getPassword(),userInfo.getPassword())){
throw new BlogException("用戶密碼錯(cuò)誤");
}
//密碼正確
Map<String,Object> map=new HashMap<>();
map.put("id",userInfo.getId());
map.put("name",userInfo.getUserName());
String token = JwtUtils.genToken(map);
return new UserLoginResponse(userInfo.getId(),token);
}使?測試類給密碼123456?成密?,寫到數(shù)據(jù)庫:

測試:
當(dāng)密碼輸入123456時(shí),可以登錄
當(dāng)輸入不為123456時(shí)提示密碼輸入錯(cuò)誤,

加密成功
到此這篇關(guān)于Spring 后端安全雙劍JWT 無狀態(tài)認(rèn)證 + 密碼加鹽加密實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)spring jwt無狀態(tài)認(rèn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot實(shí)現(xiàn)JWT 認(rèn)證的項(xiàng)目實(shí)踐
- Spring?Clou整合?Security?+?Oauth2?+?jwt實(shí)現(xiàn)權(quán)限認(rèn)證的詳細(xì)過程
- springSecurity自定義登錄接口和JWT認(rèn)證過濾器的流程
- JWT登錄認(rèn)證Springboot詳解
- SpringSecurity+jwt+captcha登錄認(rèn)證授權(quán)流程總結(jié)
- 使用Spring Security和JWT實(shí)現(xiàn)安全認(rèn)證機(jī)制
- springboot+vue前后端分離項(xiàng)目中使用jwt實(shí)現(xiàn)登錄認(rèn)證
- Springboot整合Shiro之加鹽MD5加密的方法
相關(guān)文章
SpringBoot 整合 Netty 多端口監(jiān)聽的操作方法
Netty提供異步的、基于事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架,用以快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò) IO 程序,是目前最流行的 NIO 框架,這篇文章主要介紹了SpringBoot 整和 Netty 并監(jiān)聽多端口,需要的朋友可以參考下2023-10-10
在Spring中給HttpServletRequest添加自定義Header的完整指南
在Spring應(yīng)用開發(fā)中,我們經(jīng)常需要在請求處理過程中添加自定義的 HTTP Header,由于HttpServletRequest對象在設(shè)計(jì)上是只讀的,直接添加Header會(huì)遇到困難,本文將詳細(xì)介紹如何通過Filter和RequestWrapper的方式優(yōu)雅地解決這個(gè)問題,需要的朋友可以參考下2025-10-10
解析SpringBoot中@Autowire注解的實(shí)現(xiàn)原理
在開發(fā)Java項(xiàng)目時(shí),依賴注入是一種常見的實(shí)現(xiàn)方式,SpringBoot框架通過@Autowired注解來實(shí)現(xiàn)依賴注入的功能,本文將介紹SpringBoot中 Autowired注解實(shí)現(xiàn)的原理2023-06-06
Mybatis resultType返回結(jié)果為null的問題排查方式
這篇文章主要介紹了Mybatis resultType返回結(jié)果為null的問題排查方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

