RuoYi-Vue登录模块改造实录:当Spring Security遇上国密SM4
RuoYi-Vue安全升级实战:Spring Security与SM4国密加密的无缝融合
在数字化转型加速的今天,数据安全已成为企业级应用不可忽视的核心需求。作为国内广泛使用的快速开发框架,RuoYi-Vue默认采用Spring Security提供的安全机制,但在特定行业场景下,尤其是涉及政务、金融等领域时,国密算法的合规性要求往往成为技术选型的硬性指标。本文将深入探讨如何在不破坏Spring Security原有安全架构的前提下,将SM4国密算法优雅地集成到认证流程中,实现从传输到存储的全链路加密合规。
1. 国密算法与Spring Security的融合设计
国密算法(SM系列)是由国家密码管理局颁布的一系列商用密码标准,其中SM4作为分组对称加密算法,在安全性上与AES相当,但更符合国内监管要求。与常见的改造误区不同,我们并非要替换Spring Security的整个加密体系,而是在其认证流程的关键节点插入SM4的解密逻辑。
典型集成痛点分析:
- 前端加密传输与后端解密验证的密钥一致性
- 密码修改场景下的双向加密处理
- 与原有BCrypt密码存储机制的兼容
- 过滤器链中认证时机的精准把控
在RuoYi-Vue的默认实现中,密码传输采用明文或简单哈希,这显然无法满足高安全场景。我们的改造目标是在以下三个关键环节引入SM4:
- 用户登录时的密码传输加密
- 密码修改时的旧密码验证与新密码加密
- 用户注册时的初始密码处理
2. 后端核心改造实战
2.1 依赖引入与SM4工具类封装
首先需要引入BouncyCastle提供的国密算法支持:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency>随后构建SM4加解密工具类,关键点在于处理ECB模式下的PKCS5Padding:
public class Sm4Util { private static final String ALGORITHM_NAME = "SM4"; private static final String ALGORITHM_NAME_ECB = "SM4/ECB/PKCS5Padding"; public static String decryptEcb(String cipherText, String hexKey) throws Exception { byte[] keyData = ByteUtils.fromHexString(hexKey); byte[] cipherData = ByteUtils.fromHexString(cipherText); Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB, "BC"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyData, ALGORITHM_NAME)); return new String(cipher.doFinal(cipherData), StandardCharsets.UTF_8); } }注意:实际生产环境中应将hexKey存储在配置中心或使用密钥管理系统,避免硬编码
2.2 认证流程改造关键点
在UsernamePasswordAuthenticationFilter的处理过程中,我们需要在密码验证前插入解密步骤:
public class LoginService { public String login(String username, String encryptedPassword) { // SM4解密 String rawPassword = Sm4Util.decryptEcb(encryptedPassword, hexKey); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, rawPassword); // 后续Spring Security标准认证流程 authenticationManager.authenticate(token); // ...生成token等逻辑 } }密码修改场景需要特别注意新旧密码的双向处理:
@PutMapping("/updatePwd") public AjaxResult updatePwd(String encryptedOldPwd, String encryptedNewPwd) { String oldPwd = Sm4Util.decryptEcb(encryptedOldPwd, hexKey); String newPwd = Sm4Util.decryptEcb(encryptedNewPwd, hexKey); if (!passwordEncoder.matches(oldPwd, currentPwd)) { throw new BusinessException("旧密码校验失败"); } userService.updatePassword(passwordEncoder.encode(newPwd)); }3. 前端适配方案
3.1 加密模块集成
前端采用sm-crypto库实现同步加密:
import { sm4 } from 'sm-crypto' const encryptPassword = (password, key) => { return sm4.encrypt(password, key) }3.2 登录流程改造
关键是在发起请求前完成加密处理:
async handleLogin() { const publicKey = await getPublicKey() this.loginForm.password = encryptPassword( this.loginForm.password, publicKey ) login(this.loginForm).then(response => { // 处理登录结果 }) }对于修改密码和注册流程,需要同样的加密处理:
async submitChangePwd() { const { oldPassword, newPassword } = this.form const key = await getPublicKey() const params = { oldPassword: encryptPassword(oldPassword, key), newPassword: encryptPassword(newPassword, key) } updatePassword(params).then(() => { this.$message.success('修改成功') }) }4. 安全增强与性能优化
4.1 密钥管理策略
推荐采用动态密钥方案替代固定密钥:
@GetMapping("/sm4/key") public String generateTempKey() { String sessionKey = KeyGenerator.getInstance("SM4").generateKey(); redisTemplate.opsForValue().set( "sm4:"+SecurityUtils.getUserId(), sessionKey, 5, TimeUnit.MINUTES ); return sessionKey; }4.2 性能优化方案
针对高频认证场景,可采用以下优化策略:
算法加速:
// 启用BC硬件加速 static { Security.addProvider(new BouncyCastleProvider()); Security.setProperty("crypto.policy", "unlimited"); }缓存策略:
@Cacheable(value = "sm4Cache", key = "#cipherText") public String decryptWithCache(String cipherText) { return Sm4Util.decryptEcb(cipherText, key); }连接池优化:
# 适当增大Tomcat线程池 server.tomcat.max-threads=200 server.tomcat.min-spare-threads=20
4.3 审计与监控
增强安全审计功能:
@Aspect @Component public class SecurityAuditAspect { @AfterReturning(pointcut = "execution(* *..login(..))", returning = "result") public void auditLogin(JoinPoint jp, Object result) { String username = (String) jp.getArgs()[0]; log.info("登录成功审计 - 用户:{}", username); // 发送审计事件... } }5. 兼容性处理与异常场景
5.1 多算法兼容方案
为平稳过渡,可设计多算法支持策略:
public PasswordDecoder passwordDecoder() { return new PasswordDecoder() { @Override public String decode(String encrypted) { try { // 尝试SM4解密 return Sm4Util.decryptEcb(encrypted, key); } catch (Exception e) { // 降级处理 return encrypted; } } }; }5.2 典型异常处理
完善各类异常场景的应对:
try { return sm4.decrypt(cipherText); } catch (InvalidCipherTextException e) { throw new CryptoException("SM4解密失败,密文格式异常"); } catch (IllegalStateException e) { throw new CryptoException("SM4初始化失败,请检查BouncyCastle配置"); }在前端统一处理加密异常:
async encryptedRequest(apiFunc, params) { try { const key = await getPublicKey() const encryptedParams = encryptParams(params, key) return await apiFunc(encryptedParams) } catch (error) { if (error.message.includes('SM4')) { showError('加密服务异常,请刷新重试') } throw error } }经过完整改造后,系统安全架构同时具备:
- 传输层:SM4国密算法保障
- 存储层:BCrypt强哈希保护
- 认证层:Spring Security完整机制
- 合规性:满足等保2.0相关要求
