基于 Node.js 和 Spring Boot 的 RSA 加密登录实践

发布于:2025-04-10 ⋅ 阅读:(32) ⋅ 点赞:(0)

在当今的互联网应用开发中,用户数据的安全性至关重要。登录功能作为用户进入系统的第一道防线,其安全性更是不容忽视。本文将介绍一种基于 RSA 加密的登录方案,前端使用 Node.js 的 node-forge 库对密码进行公钥加密,后端使用 Spring Boot 的 RSUtils 工具类进行私钥解密,从而确保用户密码在传输过程中的安全性。

一、RSA 加密原理简介

RSA 是一种非对称加密算法,它使用一对密钥:公钥和私钥。公钥用于加密数据,私钥用于解密数据。在登录场景中,前端使用公钥对用户密码进行加密,然后将加密后的密码发送到后端。后端使用私钥对加密的密码进行解密,从而获取原始密码,再进行后续的验证操作。

二、前端实现:使用 Node.js 的 node-forge 加密密码

(一)引入 node-forge 库

在前端项目中,首先需要引入 node-forge 库。可以通过 npm 安装:

bash复制

npm install node-forge

然后在代码中引入:

JavaScript复制

import forge from 'node-forge';

(二)获取公钥

前端登录界面

<template>
  <div class="about">
    <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">

      <el-form-item label="活动名称" prop="name">
        <el-input v-model="ruleForm.name"/>
      </el-form-item>

      <el-form-item label="密码" prop="password">
        <el-input v-model="ruleForm.password" show-password/>
      </el-form-item>


      <el-form-item>
        <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button>
        <el-button @click="resetForm('ruleForm')">重置</el-button>
      </el-form-item>
    </el-form>


  </div>
</template>

公钥需要从后端获取。后端可以通过某种方式(如 API)将公钥发送到前端。前端将公钥存储起来,以便后续使用。

<script>
//导入node-forge
import forge from 'node-forge'
import JSEncrypt from 'jsencrypt';

export default {
  data() {
    return {
      publicKey: '',
      ruleForm: {
        name: 'chj',
        password: 'gb123456'
      }
    };
  },
  created() {
    this.initPublicKey();
  },
  methods: {
    initPublicKey() {
      this.axios.get('http://localhost:9999/api/getrsa').then(res => {
        this.publicKey = res.data.publicKey;
        console.log(this.publicKey);
      });
    }
   }
   
}
</script>

(三)使用公钥加密密码

当用户输入密码并点击登录按钮时,前端使用存储的公钥对密码进行加密,将加密后的密码作为登录请求的一部分发送到后端

  submitForm(formName) {
      //获取publicKeyObj对象
      var publicKeyObj = forge.pki.publicKeyFromPem(this.publicKey);
      console.log("原始数据",this.ruleForm.password);
      //对URI组件统一编码
      var encode = encodeURIComponent(this.ruleForm.password);
      console.log("编码后数据",encode)
      //对字符串加密处理
      var encrypted = publicKeyObj.encrypt(encode, 'RSA-OAEP', {
        md: forge.md.sha256.create(),
        mgf1:{
          md: forge.md.sha1.create()
        }
      });
      console.log("加密后数据",encrypted);
      //工具类转码
      var encryptedBase64 = forge.util.encode64(encrypted);
      console.log("base64后得编码",encryptedBase64);
      //发送请求携带加密的串
      this.axios.post('http://localhost:9999/api/login', {
        name: this.ruleForm.name,
        password: encryptedBase64
      }).then(res => {
        console.log(res);
      });
    },
    resetForm(formName) {
      this.$refs[formName].resetFields();
    }

三、后端实现:Spring Boot 结合 RSUtils 工具类解密密码

工具类RSUtils

package org.example.util;

import com.sun.crypto.provider.SunJCE;

import java.nio.charset.StandardCharsets;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.io.ByteArrayOutputStream;
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.security.*;
import java.util.*;


public class RSAUtils {

    public static final String KEY_ALGORITHM = "RSA";
    private static final int MAX_DECRYPT_BLOCK = 256;


    //Base64解码
    private static final Base64.Decoder decoder64 = Base64.getDecoder();
    //Base64编码
    private static final Base64.Encoder encoder64 = Base64.getEncoder();

    /**
     * 生成公私钥
     *
     * @param keySize 密钥长度(如2048)
     * @return 密钥对
     * @throws NoSuchAlgorithmException
     */
    public static SecretKey generateSecretKey(int keySize) throws NoSuchAlgorithmException {
        SunJCE provider=new com.sun.crypto.provider.SunJCE();
        Security.addProvider(provider);
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(keySize, new SecureRandom());
        KeyPair pair = keyGen.generateKeyPair();
        PrivateKey privateKey = pair.getPrivate();
        PublicKey publicKey = pair.getPublic();
        return new SecretKey(encoder64.encodeToString(publicKey.getEncoded()), encoder64.encodeToString(privateKey.getEncoded()));
    }

    /**
     * 私钥解密
     */
    public static String decryptByPrivateKey(String str, String key) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT);

        PrivateKey privateKey = strToPrivateKey(key);
        cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);
        byte[] data = decryptBASE64(str);

        byte[] bytes = dataSegment(data, cipher, MAX_DECRYPT_BLOCK);
        return new String(bytes, StandardCharsets.UTF_8);
    }

    /**
     * 对数据进行分段加密或解密
     */
    private static byte[] dataSegment(byte[] data, Cipher cipher, int maxBlock) throws Exception{
        byte[] toByteArray;
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        int i = 0;
        byte[] cache;
        // 对数据分段加密解密
        while (inputLen - offSet > 0) {
            int var = inputLen - offSet > maxBlock ? maxBlock : inputLen - offSet;
            cache = cipher.doFinal(data, offSet, var);
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * maxBlock;
        }
        toByteArray = out.toByteArray();
        out.close();
        return toByteArray;
    }


    /**
     * 获取私钥
     */
    private static PrivateKey strToPrivateKey(String str) throws Exception {
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(decryptBASE64(str));
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        return privateKey;
    }

    /**
     * 字符串转字节数组
     */
    private static byte[] decryptBASE64(String str) {
        return Base64.getDecoder().decode(str);
    }

    public static class SecretKey {
        private String publicKey;
        private String privateKey;

        public SecretKey(String publicKey, String privateKey) {
            this.publicKey = publicKey;
            this.privateKey = privateKey;
        }

        public String getPublicKey() {
            return publicKey;
        }

        public String getPrivateKey() {
            return privateKey;
        }

        @Override
        public String toString() {
            return "SecretKey{" +
                    "publicKey='" + publicKey + '\'' +
                    ", privateKey='" + privateKey + '\'' +
                    '}';

        }
    }

}

后端控制器类代码:

package org.example.controller;

import org.example.model.Users;
import org.example.util.RSAUtils;
import org.example.util.RSAtils;
import org.springframework.web.bind.annotation.*;


import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

import static java.util.regex.Pattern.compile;

@CrossOrigin
@RequestMapping("/api")
@RestController
public class RSAController {

    //密钥对象
    private RSAUtils.SecretKey secretKey;

    //通过构造函数创建对象
    public RSAController() throws NoSuchAlgorithmException {
        System.out.println("----------------");
        secretKey = RSAUtils.generateSecretKey(2048);
    }

    @GetMapping("/getrsa")
    public Map<String,Object> getrsa() throws NoSuchAlgorithmException {
        Map<String,Object> map = new HashMap<>();
        String temp="-----BEGIN PUBLIC KEY-----\n" + secretKey.getPublicKey() + "\n-----END PUBLIC KEY-----";
        map.put("publicKey",temp);
       // map.put("privateKey",secretKey.getPrivateKey());
        return map;
    }

    @PostMapping("/login")
    public Map<String,Object> login(@RequestBody Users users) throws Exception {
        //工具类解密
        String decryptedData = RSAUtils.decryptByPrivateKey(users.getPassword(),secretKey.getPrivateKey());
        //创建对象
        Map<String, Object> map = new HashMap<>();
        //设置解密成功响应的结果
        map.put("pass", decryptedData);
        return map;

    }
}

model实体类

package org.example.model;

import java.io.Serializable;

public class Users implements Serializable {

    private String name;
    private String password;

    //空的构造函数
    public Users() {
    }

    //构造函数
    public Users(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

四、安全性考虑

  1. 密钥管理:密钥对的生成和存储需要严格管理。公钥可以公开,但私钥必须保密,不能泄露给任何未经授权的人员或系统。

  2. 传输安全:虽然密码在传输过程中被加密,但仍然需要使用 HTTPS 协议来确保整个通信过程的安全性,防止中间人攻击。

  3. 前端安全:前端代码可能会被篡改,因此需要确保前端代码的完整性和安全性。可以通过代码签名等方式来防止恶意代码注入。

五、测试结果

六、总结

本文介绍了一种基于 RSA 加密的登录方案,前端使用 Node.js 的 node-forge 库对密码进行公钥加密,后端使用 Spring Boot 的 RSUtils 工具类进行私钥解密。通过这种方式,可以有效保护用户密码在传输过程中的安全性。在实际应用中,还需要结合其他安全措施,如 HTTPS、密钥管理等,以确保系统的整体安全性。


网站公告

今日签到

点亮在社区的每一天
去签到