若依 Vue3 前端分离 3.8.8 版集成 jsencrypt 实现密码加密传输方式

avatar
作者
猴君
阅读量:0

一、问题展示

在若依的 Vue 前端分离版中登录密码和修改密码时可以发现密码是明文传输的,超管重置用户密码时同样如此,如下图所示:

可以发现密码全部都是明文传输,十分不安全,必须对传输的密码进行加密传输。

二、解决方法

在项目中集成 jsencrypt 实现密码加密传输方式。

2.1 jsencrypt 实现密码加密传输流程

  1. 后端生成随机公钥和私钥
  2. 前端拿到公钥,集成 jsencrypt 实现密码加密
  3. 前端传输加密后的密码给后端
  4. 后端通过私钥对加密后的密码进行解密,然后验证密码

​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

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!