一、问题展示
在若依的 Vue 前端分离版中登录密码和修改密码时可以发现密码是明文传输的,超管重置用户密码时同样如此,如下图所示:
可以发现密码全部都是明文传输,十分不安全,必须对传输的密码进行加密传输。
二、解决方法
在项目中集成 jsencrypt 实现密码加密传输方式。
2.1 jsencrypt 实现密码加密传输流程
- 后端生成随机公钥和私钥
- 前端拿到公钥,集成 jsencrypt 实现密码加密
- 前端传输加密后的密码给后端
- 后端通过私钥对加密后的密码进行解密,然后验证密码
2.2 若依官网文档
若依的官网有集成 jsencrypt 实现密码加密传输的相关文档,可以参考:
集成jsencrypt实现密码加密传输方式 | RuoYi
2.3 添加后端代码
2.3.1 创建RsaUtils类
在 common 模块下面 utils 包下的 sign 包中添加 RsaUtils.java,用于 RSA 加密解密。
package com.uam.common.utils.sign;
import org.apache.commons.codec.binary.Base64;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* @Project:
* @Package: com.uam.common.utils.sign
* @Author:
* @CreateTime:
* @Version: 1.0
* @Description: RSA加密解密
*/
@Component
public class RsaUtils {
private static final RsaKeyPair rsaKeyPair = new RsaKeyPair();
public static RsaKeyPair getRsaKeyPair() {
return rsaKeyPair;
}
/**
* 私钥解密
*
* @param text 待解密的文本
* @return 解密后的文本
*/
public static String decryptByPrivateKey(String text) throws Exception {
return decryptByPrivateKey(rsaKeyPair.getPrivateKey(), text);
}
/**
* 公钥解密
*
* @param publicKeyString 公钥
* @param text 待解密的信息
* @return 解密后的文本
*/
public static String decryptByPublicKey(String publicKeyString, String text) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] result = cipher.doFinal(Base64.decodeBase64(text));
return new String(result);
}
/**
* 私钥加密
*
* @param privateKeyString 私钥
* @param text 待加密的信息
* @return 加密后的文本
*/
public static String encryptByPrivateKey(String privateKeyString, String text) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(text.getBytes());
return Base64.encodeBase64String(result);
}
/**
* 私钥解密
*
* @param privateKeyString 私钥
* @param text 待解密的文本
* @return 解密后的文本
*/
public static String decryptByPrivateKey(String privateKeyString, String text) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(Base64.decodeBase64(text));
return new String(result);
}
/**
* 公钥加密
*
* @param publicKeyString 公钥
* @param text 待加密的文本
* @return 加密后的文本
*/
public static String encryptByPublicKey(String publicKeyString, String text) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] result = cipher.doFinal(text.getBytes());
return Base64.encodeBase64String(result);
}
/**
* 构建RSA密钥对
*
* @return 生成后的公私钥信息
*/
@Bean
public void generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
rsaKeyPair.setPrivateKey(privateKeyString);
rsaKeyPair.setPublicKey(publicKeyString);
}
/**
* RSA密钥对对象
*/
public static class RsaKeyPair {
private String publicKey;
private String privateKey;
public RsaKeyPair() {
}
public RsaKeyPair(String publicKey, String privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public String getPublicKey() {
return publicKey;
}
public String getPrivateKey() {
return privateKey;
}
}
}
在项目中,
RsaUtils
类通过使用
@Component
注解被注册为 Spring 容器中的一个组件。该类中的
generateKeyPair()
方法利用
@Bean
注解被标记为一个需要由 Spring 容器管理的 Bean。因此,当应用程序启动时,Spring 容器会自动调用
generateKeyPair()
方法,确保每次启动时都能生成一对唯一的 RSA 密钥。这一机制保证了应用在其生命周期内使用的 RSA 密钥对是一致的,无需重复生成。然而,一旦应用程序重启,
generateKeyPair()
方法将再次被调用,从而生成新的密钥对,确保密钥的安全性和唯一性。
2.3.2 添加前端获取公钥的接口
在 admin 模块下的 com.uam.web.controller.system 包中 SysLoginController 类中添加 RESTful API 接口 publicKey(),用于当客户端通过 GET 请求访问 publicKey 路径时,服务器会返回一个包含公钥的 RsaUtils.RsaKeyPair 对象。这个对象中的公钥可以被前端用来对密码进行加密。
/**
* 前端获取公钥,用来给密码加密
*
* @return 秘钥对象
*/
@GetMapping("/publicKey")
public RsaUtils.RsaKeyPair publicKey() throws NoSuchAlgorithmException {
RsaUtils.RsaKeyPair rsaKeyPair = new RsaUtils.RsaKeyPair();
rsaKeyPair.setPublicKey(RsaUtils.getRsaKeyPair().getPublicKey());
return rsaKeyPair;
}
2.3.3 登录方法进行rsa解密
在 2.3.2 中同样的类中,对 login() 方法进行修改,提前对前端传过来的加密密码进行解密,避免影响后面程序运行。
/**
* 登录方法
*
* @param loginBody 登录信息
* @return 结果
*/
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody) throws Exception {
AjaxResult ajax = AjaxResult.success();
// 生成令牌
String token = loginService.login(loginBody.getUsername(),
RsaUtils.decryptByPrivateKey(loginBody.getPassword()),
// loginBody.getPassword(),
loginBody.getCode(), loginBody.getUuid());
ajax.put(Constants.TOKEN, token);
return ajax;
}
2.3.4 重置密码方法进行rsa解密
在 admin 模块下的 com.uam.web.controller.system 包中 SysProfileController 类中的 updatePwd 方法中添加下面两行代码,对前端传过来的加密密码进行解密。
// 密码解密
oldPassword = RsaUtils.decryptByPrivateKey(oldPassword);
newPassword = RsaUtils.decryptByPrivateKey(newPassword);
2.3.5 超管重置用户密码方法进行rsa解密
在 admin 模块下的 com.uam.web.controller.system 包中 SysUserController 类中的 resetPwd 方法中添加下面一行代码,对前端传过来的加密密码进行解密。
user.setPassword(RsaUtils.decryptByPrivateKey(user.getPassword()));
2.3.6 配置白名单
在 framework 模块下的 com.uam.framework.config 包中 SecurityConfig 类中的第 114 行原有的 "/login", "/register", "/captchaImage" 后面添加 "/publicKey" ,这样可以确保任何用户都可以获取用于加密的公钥,而不需要进行身份验证。
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage","/publicKey").permitAll()
2.3.7 测试接口
可以使用 Postman 来测试刚刚的 publicKey 接口能否成功被调用。端口号如果没有进行修改默认为 8080。
http://localhost:8080/publicKey
可以发现,公钥获取成功,但私钥是空的。前端只需要使用公钥来加密密码,而无需私钥。因此,发送给前端的密钥对对象仅包含公钥,不包含私钥。这样,即使公钥被公开,也不会影响系统的安全性,因为只有私钥才能解密数据。
2.4 添加前端代码
2.4.1 添加获取公钥接口路径
在 src\api\login.js 中添加获取公钥接口路径代码。
// 获取公钥
export function getPublicKey() {
return request({
url: '/publicKey',
method: 'get',
})
}
2.4.2 更改加密方法
在 src\utils\jsencrypt.js 中修改 encrypt 方法如下:
// 加密
export function encrypt(txt, RsaKeyPair) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(RsaKeyPair.publicKey) // 设置公钥
return encryptor.encrypt(txt) // 对数据进行加密
}
可以将若依自带的公钥和私钥注释掉。
2.4.3 登录时对密码加密
在 src\store\modules\user.js 中的 actions: {} 中添加下面的代码,可以将原来的 login 方法注释掉。
async getPublicKey() {
try {
const response = await getPublicKey();
return response; // 返回获取到的秘钥对象
} catch (error) {
throw error; // 如果获取秘钥对象失败,则抛出错误
}
},
async login(userInfo) {
try {
const username = userInfo.username.trim();
const code = userInfo.code;
const uuid = userInfo.uuid;
// 获取秘钥对象
const rsaKeyPair = await this.getPublicKey();
// console.log('Public Key:', rsaKeyPair.publicKey);
// 使用公钥加密密码
const encryptedPassword = encrypt(userInfo.password, rsaKeyPair);
// 使用加密后的密码登录
const res = await login(username, encryptedPassword, code, uuid);
setToken(res.token);
this.token = res.token;
return res; // 可以返回res以便进一步处理或捕获错误
} catch (error) {
throw error; // 抛出错误以便外部捕获
}
},
一定要添加引用:
import { login, logout, getInfo, getPublicKey } from '@/api/login' // 在原来的基础上加上 getPublicKey
import { encrypt } from '@/utils/jsencrypt' // 额外添加
2.4.4 重置密码时对密码加密
在 src\views\system\user\profile\resetPwd.vue 中添加下面的代码,可以将原来的 submit、close 方法注释掉。
const getPublicKeyWrapper = () => {
return new Promise((resolve, reject) => {
getPublicKey()
.then(res => {
resolve(res);
})
.catch(error => {
reject(error);
});
});
};
const submit = () => {
proxy.$refs.pwdRef.validate(valid => {
if (valid) {
getPublicKeyWrapper().then(res => {
const rsaKeyPair = res;
const oldPassword = encrypt(user.oldPassword, rsaKeyPair);
const newPassword = encrypt(user.newPassword, rsaKeyPair);
updateUserPwd(oldPassword, newPassword).then(response => {
proxy.$modal.msgSuccess("修改成功");
});
});
}
});
};
const close = () => {
proxy.$tab.closePage();
};
一定要添加引用:
import { getPublicKey } from '@/api/login'
import { encrypt } from '@/utils/jsencrypt'
2.4.5 超管重置用户密码时对密码加密
在 src\views\system\user\index.vue 中添加 getPublicKeyWrapper 并修改 handleResetPwd。
const getPublicKeyWrapper = () => {
return new Promise((resolve, reject) => {
getPublicKey()
.then(res => {
resolve(res);
})
.catch(error => {
reject(error);
});
});
};
/** 重置密码按钮操作 */
// function handleResetPwd(row) {
const handleResetPwd = (row) => {
proxy.$prompt('请输入"' + row.userName + '"的新密码', "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
closeOnClickModal: false,
inputPattern: /^.{5,20}$/,
inputErrorMessage: "用户密码长度必须介于 5 和 20 之间",
}).then(({ value }) => {
getPublicKeyWrapper().then(res => {
const rsaKeyPair = res;
resetUserPwd(row.userId, encrypt(value, rsaKeyPair)).then(response => {
proxy.$modal.msgSuccess("修改成功,新密码是:" + value);
});
});
}).catch(() => {});
};
一定要添加引用:
import { getPublicKey } from '@/api/login'
import { encrypt } from '@/utils/jsencrypt'
三、修改结果
可以发现所有密码均已加密。
四、参考
https://blog.csdn.net/weixin_56567361/article/details/124961493
https://blog.csdn.net/HelloWorld20161112/article/details/130906994
https://doc.ruoyi.vip/ruoyi-vue/document/cjjc.html#%E9%9B%86%E6%88%90jsencrypt%E5%AE%9E%E7%8E%B0%E5%AF%86%E7%A0%81%E5%8A%A0%E5%AF%86%E4%BC%A0%E8%BE%93%E6%96%B9%E5%BC%8F
版权归原作者 疯狂的格式化 所有, 如有侵权,请联系我们删除。