Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现

Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现

什么是RSA?

RSA算法是一种非对称加密算法,与对称加密算法不同的是,RSA算法有两个不同的密钥,一个是公钥,一个是私钥

RSA公开密钥密码体制是一种使用不同的加密密钥与解密密钥,"由已知加密密钥推导出解密密钥在计算上是不可行的"密码体制

在公开密钥密码体制中,加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的。加密算法E和解密算法D也都是公开的。虽然解密密钥SK是由公开密钥PK决定的,但却不能根据PK计算出SK

正是基于这种理论,1978年出现了著名的RSA算法,它通常是先生成一对RSA密钥,其中之一是保密密钥,由用户保存;另一个为公开密钥,可对外公开,甚至可在网络服务器中注册。为提高保密强度,RSA密钥至少为500位长。这就使加密的计算量很大。为减少计算量,在传送信息时,常采用传统加密方法与公开密钥加密方法相结合的方式,即信息采用改进的DES或IDEA对话密钥加密,然后使用RSA密钥加密对话密钥和信息摘要。对方收到信息后,用不同的密钥解密并可核对信息摘要

安全需求介绍

背景:在安全扫描过程中,系统被检测到存在中危漏洞,影响了用户登录环节的信息安全性

漏洞描述

Web程序在处理用户登录过程中,针对用户名和口令在传输前未采用加密。用户名和口令一旦被恶意攻击者嗅探或暴力破解得到后,可以直接利用它去登录系统。

解决方案:建议对用户名、口令等参数值采用强加密算法,如SHA512、RSA、AES、国密算法等,避免使用MD5、Base64、DES、3DES等弱算法。

问题分析:未加密的敏感信息在传输过程中易被拦截,尤其是在公共网络环境下。这使得系统的认证机制成为攻击目标,存在较高的安全隐患

需求实现

  1. 在前端使用安全加密算法对用户名和密码进行加密,防止信息裸露传输

  2. 后端解密用户信息并进行验证,同时保障传输数据的完整性与安全性

前后端交互流程

基于RSA加密的登录流程,Web端和服务端的交互过程详细步骤:

  1. 用户发起登录请求

    • 用户在Web端输入登录操作,Web端开始处理登录流程

    • Web端向服务端发送一个请求,要求获取RSA加密所需的公钥

  2. 服务端返回公钥

    • 服务端接收到公钥请求后,从系统中读取公钥信息,并将公钥发送回Web端。这个步骤的响应无需校验
  3. Web端加密用户凭据

    • Web端使用获取到的公钥,对用户的用户名和密码进行加密。加密后的数据用于安全传输,避免敏感信息在网络中被截获

    • Web端将加密后的用户名和密码通过HTTPS协议发送给服务端。HTTPS协议提供了额外的安全层,防止中间人攻击

  4. 服务端解密并验证

    • 服务端接收到加密的数据后,使用私钥对加密内容进行解密。私钥是严格保密的,只在服务端保存和使用
    • 服务端对解密后的用户名和密码进行验证,检查凭据是否正确并符合登录要求
  5. 返回登录结果

    • 服务端验证完成后,将登录验证的结果返回给Web端。结果可以是登录成功或失败的信息
  6. Web端处理响应

    • Web端接收并处理服务端返回的登录结果,并显示相应的提示或进行后续操作

前端使用 RSA 加密密码

在前端 Vue.js 中,使用 jsencrypt 库对用户名密码进行加密,并将加密后的用户名密码发送给后端

安装 jsencrypt库

在Vue.js 项目中运行以下命令来安装 jsencrypt

bash 复制代码
npm install jsencrypt --save

实现敏感信息加密

在 Vue.js 登录页面中,对敏感信息进行加密,然后发送给后端:

javascript 复制代码
// 引入 jsencrypt 和 Base64 编码库
import JSEncrypt from 'jsencrypt';
// 需要安装 js-base64 库
import { Base64 } from 'js-base64'; 

export default {
  data() {
    return {
      username: '', // 用户输入的用户名
      password: ''  // 用户输入的密码
    };
  },
  methods: {
    /**
     * 使用 RSA 公钥加密字符串
     * @param {string} str - 需要加密的字符串
     * @returns {string|null} - 加密后的字符串,若失败返回 null
     */
    encryptString(str) {
      // 初始化 JSEncrypt
      const encrypt = new JSEncrypt();
      // 设置公钥
      const publicKey = `
        -----BEGIN PUBLIC KEY-----
        ...公钥内容...
        -----END PUBLIC KEY-----
      `;
      encrypt.setPublicKey(publicKey);

      // 加密字符串并返回加密结果
      return encrypt.encrypt(str);
    },

    /**
     * 用户登录逻辑
     */
    login() {
      // 获取用户输入的用户名和密码
      const username = this.username.trim(); // 去除多余空格
      const password = this.password.trim();

      // 验证用户名和密码是否为空
      if (!username || !password) {
        this.$message.error('用户名或密码不能为空'); // 提示用户
        return;
      }

      // 加密用户名和密码
      const encryptedUsername = this.encryptString(username);
      const encryptedPassword = this.encryptString(password);

      // 验证加密是否成功
      if (!encryptedUsername || !encryptedPassword) {
        this.$message.error('加密失败,请检查公钥配置');
        return;
      }

      // Base64 编码的主要作用是将加密后的数据转换成由可见 ASCII 字符组成的字符串,以便在网络传输或存储时避免因特殊字符引发的兼容性问题
      const base64EncodedUsername = Base64.encode(encryptedUsername);
      const base64EncodedPassword = Base64.encode(encryptedPassword);

      // 发送登录请求到后端
      this.$axios.post('/api/login', {
        username: base64EncodedUsername, // 加密后的用户名
        password: base64EncodedPassword  // 加密后的密码
      }).then(response => {
        // 登录成功后的处理逻辑
        if (response.data.success) {
          this.$message.success('登录成功');
          // 跳转到主页面或其他逻辑
        } else {
          this.$message.error(response.data.message || '登录失败');
        }
      }).catch(error => {
        // 请求失败的处理逻辑
        console.error('登录请求失败:', error);
        this.$message.error('登录失败,请稍后重试');
      });
    }
  }
};

服务器端生成RSA的公私钥文件

Windows环境 生成rsa的公私钥文件

  1. 安装 OpenSSL :下载并安装 OpenSSL for Windows,可以从 OpenSSL 官网 下载适合的版本。

  2. 生成 RSA 私钥

    • 在安装目录的bin目录下面,打开命令提示符(cmd

    • 运行以下命令生成一个2048位的私钥文件 private_key.pem

      bash 复制代码
      openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
  3. 生成 RSA 公钥

    • 使用生成的私钥来提取公钥,运行以下命令:

      bash 复制代码
      openssl rsa -pubout -in private_key.pem -out public_key.pem

Linux环境 生成rsa的公私钥文件

使用RSA算法生成pem格式的私钥文件,指定密钥长度2048

bash 复制代码
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048

使用RSA算法在上述私钥文件基础上生成pem格式的公钥文件

bash 复制代码
openssl rsa -pubout -in private_key.pem -out public_key.pem

执行完成后,会看到输出两份文件 private_key.pem, public_key.pem。

后端代码实现

返回给前端的公钥接口

将生成的两份密钥文件放入指定的目录中,然后在配置文件中配置公私钥文件所属路径

java 复制代码
 /**
     * 获取公钥
     */
    @GetMapping("/getPublicKey")
    public AjaxResult getPublicKey() {
        // 加载资源
        File file = new File("公钥的文件所属路径");
        if (!file.exists()) {
            throw new BusinessException("未获取到指定公钥文件");
        }

        String publicKey;
        try (InputStream inputStream = Files.newInputStream(file.toPath())) {
            publicKey = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
        } catch (IOException e) {
            log.error("读取公钥文件失败", e);
            // 返回错误信息
            return AjaxResultGenerator.error("读取公钥文件失败");
        }

        // 检查 publicKey 是否为 null 或空字符串
        if (publicKey == null || publicKey.isEmpty()) {
            return AjaxResultGenerator.error("公钥内容为空");
        }

        // 删除PEM格式的头部和尾部以及所有空格、换行符
        publicKey = publicKey.replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\\s+", "");

        return AjaxResultGenerator.success(publicKey);
    }

解密前端传入的加密字符串

前端的账户和密码进行rsa加密并且base64编码后,调用正常登录接口login()readPrivateKeyAndDecode()方法将 RSA 加密并 Base64 编码的字符串解码后,再用 RSA 私钥对解码后的数据进行解密,最终返回解密后的原始数据

pom.xml

xml 复制代码
<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>5.8.26</version>
</dependency>

登录和解密方法

java 复制代码
 /**
     * 用户名密码登录
     *
     * @param loginBody 登录信息
     * @return 结果
     */
    @PostMapping("/v1/login")
    public AjaxResult login(@RequestBody LoginBody loginBody) {

        // 使用 RSA 私钥解密
        String username = readPrivateKeyAndDecode(loginBody.getUsername());
        String password = readPrivateKeyAndDecode(loginBody.getPassword());

        loginBody.setUsername(username);
        loginBody.setPassword(password);

 	    // 执行正常的登录逻辑...
        return AjaxResultGenerator.success();
    }

    /**
     * 读取私钥并解密
     *
     * @param rsaEncoded64Str 已rsa加密并且base64编码后的字符串
     * @return
     */
    public String readPrivateKeyAndDecode(String rsaEncoded64Str) {
        // 加载资源
        File file= new File("私钥的文件所属路径");
        if (!file.exists()) {
            throw new BusinessException("未获取到指定私钥文件");
        }

        String privateKey;
        try (InputStream inputStream = Files.newInputStream(file.toPath())) {
            privateKey = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
        } catch (IOException e) {
            log.error("读取私钥文件失败", e);
            throw new BusinessException("读取私钥文件失败:" + e.getMessage());
        }

        if (StringUtils.isBlank(privateKey)) {
            throw new BusinessException("私钥内容为空");
        }

        // 删除PEM格式的头部和尾部以及所有空格、换行符
        privateKey = privateKey.replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");

        // 进行base64解密
        String decodedUsername = new String(Base64.getDecoder().decode(rsaEncoded64Str));

        // 使用 RSA 私钥解密,用到了hutool-all中的类
        return new RSA(privateKey, null)
                .decryptStr(decodedUsername, KeyType.PrivateKey);
    }

实现效果

相关推荐
RainbowSea6 分钟前
5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
java·消息队列·rabbitmq
李少兄2 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
此木|西贝2 小时前
【设计模式】原型模式
java·设计模式·原型模式
可乐加.糖2 小时前
一篇关于Netty相关的梳理总结
java·后端·网络协议·netty·信息与通信
ElasticPDF-新国产PDF编辑器2 小时前
Vue use pdf.js and Elasticpdf tutorial
vue.js·pdf
s9123601012 小时前
rust 同时处理多个异步任务
java·数据库·rust
9号达人2 小时前
java9新特性详解与实践
java·后端·面试
cg50172 小时前
Spring Boot 的配置文件
java·linux·spring boot
啊喜拔牙2 小时前
1. hadoop 集群的常用命令
java·大数据·开发语言·python·scala
anlogic3 小时前
Java基础 4.3
java·开发语言