若依 Vue3 前端分离 3.8.8 版集成 jsencrypt 实现密码加密传输方式
疯狂的格式化 2024-09-01 14:33:01 阅读 59
一、问题展示
在若依的 Vue 前端分离版中登录密码和修改密码时可以发现密码是明文传输的,超管重置用户密码时同样如此,如下图所示:
可以发现密码全部都是明文传输,十分不安全,必须对传输的密码进行加密传输。
二、解决方法
在项目中集成 jsencrypt 实现密码加密传输方式。
2.1 jsencrypt 实现密码加密传输流程
后端生成随机公钥和私钥前端拿到公钥,集成 jsencrypt 实现密码加密前端传输加密后的密码给后端后端通过私钥对加密后的密码进行解密,然后验证密码
2.2 若依官网文档
若依的官网有集成 jsencrypt 实现密码加密传输的相关文档,可以参考:
集成jsencrypt实现密码加密传输方式 | RuoYi
2.3 添加后端代码
2.3.1 创建RsaUtils类
在 common 模块下面 utils 包下的 sign 包中添加 RsaUtils.java,用于 RSA 加密解密。
<code>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 方法中添加下面一行代码,对前端传过来的加密密码进行解密。
<code>user.setPassword(RsaUtils.decryptByPrivateKey(user.getPassword()));
2.3.6 配置白名单
在 framework 模块下的 com.uam.framework.config 包中 SecurityConfig 类中的第 114 行原有的 "/login", "/register", "/captchaImage" 后面添加 "/publicKey" ,这样可以确保任何用户都可以获取用于加密的公钥,而不需要进行身份验证。
<code>// 对于登录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 中添加获取公钥接口路径代码。
<code>// 获取公钥
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'
三、修改结果
可以发现所有密码均已加密。
四、参考
<code>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
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。