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

news2024/9/18 12:24:16

一、问题展示

在若依的 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#%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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1953717.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端渲染模式

渲染的概念 在Web开发中,渲染(Rendering)是一个核心概念,指的是将应用程序的数据(data)与模板(template)结合,生成最终的HTML页面,这个页面随后会被浏览器解析…

扰动观测器DOB设计及其MATLAB/Simulink实现

扰动观测器(Disturbance Observer, DOB)是一种在控制系统中用于估计和补偿未知扰动的重要工具,以增强系统的鲁棒性和稳定性。其设计过程涉及系统建模、观测器结构设计以及控制律的调整。 扰动观测器设计原理 系统建模: 首先,需要建立被控对象的数学模型,明确系统的状态变…

深入理解synchronized(简记)

深入理解synchronized 管程synchronized对象的内存布局锁状态记录锁对象状态转换偏向锁轻量级锁锁对象转换总结 管程synchronized Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简。 对象的内存布局 对象头 Mar…

DuckDB核心模块揭秘 | 第1期 | 向量化执行引擎之Pipeline

DuckDB核心模块揭秘 | 第1期 | 向量化执行引擎之Pipeline DuckDB是一款非常火的OLAP嵌入式数据库,性能超级棒。它分为多个组件:解析器、逻辑规划器、优化器、物理规划器、执行器以及事务和存储管理层。其中解析器原语PgSQL的解析器;逻辑规划器…

skynet热更新之inject

游戏服务器的热更新是一种常见的需求,skynet可以通过inject的方式,来修改一个服务的消息处理函数,达到热更新的效果。 skynet内置服务debug_console skynet自带了一个调试控制台服务。inject注入代码需要先启动这个服务。 skynet.newservi…

linux自动化构建工具--make/makefile

目录 1.make/makefile介绍 1.1基本认识 1.2依赖关系、依赖方法 1.3具体操作步骤 1.4进一步理解 1.5默认设置 1.6make二次使用的解释 1.7两个文件的时间问题 1.8总是被执行 1.9特殊符号介绍 1.make/makefile介绍 1.1基本认识 make是一个指令,makefile是一…

Dify中语音和文字间转换问题的一种暂时注释方式

本文主要解释了Dify中语音和文字间转换可能会遇到的问题,并给出了一种暂时注释的解决方案。 一.文本转语音可能问题 本地部署文本转语音时,如果遇到如下问题,安装ffmpeg即可。但是如果安装后,重启系统还是遇到这个问题该如何办&…

02 Golang面向对象编程_20240727 课程笔记

视频课程 最近发现越来越多的公司在用Golang了,所以精心整理了一套视频教程给大家,这个是其中的第二部,后续还会有很多。 视频已经录制完成,完整目录截图如下: 课程目录 01 结构体的声明.mp402 使用var根据结构体…

Firefox扩展程序和Java程序通信

实现Firefox扩展程序,和Java RMI Client端进行通信。 在Firefox工具栏注册按钮,点击按钮后弹出Popup.html页面,引用Popup.js脚本,通过脚本向Java RMI client发送消息,Java RMI Client接收消息后转发到Java RMI Server…

Docker————数据卷容器,容器互联,镜像创建

1、Docker的数据管理 管理Docker容器中的数据,主要有两种方式:数据卷(Data Volumes)和数据卷容器(DataVolumes Containers). docker run [-i -t] [--name 容器名] 镜像名:标签 [容器启动命令]…

RK3568 Linux 平台开发系列讲解(内核入门篇):从内核的角度看外设芯片的驱动

在嵌入式 Linux 开发中,外设芯片的驱动是实现操作系统与硬件之间交互的关键环节。对于 RK3568 这样的处理器平台,理解如何从内核的角度构建和管理外设芯片的驱动程序至关重要。 1. 外设驱动的基础概念 外设驱动(Device Driver)是操作系统与硬件设备之间的桥梁。它负责控…

智能浇花机器人·设计说明

智能浇花机器人 目录: 第一章 :项目描述 1 1.1 项目简介 1 1.1.1 服务范围: 1 1.1.2 所处行业: 1 1.2 项目背景 1 1.3 创新点与项目特色 3 第二章 :设计说明书 4 2.1 主要构成: 4 2.1.1 循迹小车 4 2.1.2 机械…

网络编程——wireshark抓包、tcp粘包

目录 一、前言 1.1 什么是粘包 1.2 为什么UDP不会粘包 二、编写程序 文件树 客户端程序 服务器程序 tcp程序 头文件 makefile 三、 实验现象 四、改进实验 五、小作业 一、前言 最近在做网络芯片的驱动,验证功能的时候需要借助wireshark这个工具&…

DataX(二):DataX安装与入门

1. 官方地址 下载地址:http://datax-opensource.oss-cn-hangzhou.aliyuncs.com/datax.tar.gz 源码地址:GitHub - alibaba/DataX: DataX是阿里云DataWorks数据集成的开源版本。 2. 前置要求 Linux JDK(1.8 以上,推荐 1.8) Python(推荐 Pyt…

C语言内存函数精讲

目录 引言 1.内存分配函数malloc 2.内存释放函数free 3.内存拷贝函数memcpy 4.内存移动函数memmove 5.内存设置函数memset 6.内存比较函数memcmp 总结 引言 在C语言编程中,内存管理是核心技能之一。C语言提供了一系列内存操作函数,这些函数在动…

机器学习 第7章-贝叶斯分类器

机器学习 第7章-贝叶斯分类器 7.1 贝叶斯决策论 贝叶斯决策论(Bayesian decision theory)是概率框架下实施决策的基本方法。对分类任务来说,在所有相关概率都已知的理想情形下,贝叶斯决策论考虑如何基于这些概率和误判损失来选择最优的类别标记。下面我…

Linux Vim全能攻略:实战代码,轻松掌握文本编辑神器

1. Vim简介与安装 1.1 Vim的历史与发展 Vim(Vi IMproved)是一款高度可配置的文本编辑器,它起源于1976年由Bill Joy开发的Vi编辑器。Vi是Unix系统上最古老的文本编辑器之一,因其强大的功能和高效的编辑方式而广受欢迎。随着时间的…

流媒体服务器一:搭建RTMP流媒体服务器搭建

1 安装和测试srs流媒体服务器 服务器:SRS(Simple RTMP Server,⽀持RTMP、HTTP-FLV,HLS) 推流端:ffmpeg OBS 拉流端:ffplay VLC srs播放器 1.1 安装srs流媒体服务器 官网 SRS (Simple Realtime Server) | SRS 码…

【一图流】Git下载与安装教程

下载Git Git官网:https://git-scm.com/?hlzh-cn 安装Git

全栈嵌入式C++、STM32、Modbus、FreeRTOS和MQTT协议:工业物联网(IIoT)可视化系统设计思路(附部分代码解析)

项目概述 随着工业4.0时代的到来,工业物联网(IIoT)在提高生产效率、降低运营成本和实现智能制造方面得到了广泛应用。本项目旨在开发一个全面的工业物联网监控系统,能够实时监测设备的温度、压力、振动和电流等参数,并…