SpringBoot前后端RSA加解密问题

今天的问题:

个人在是使用RSA进行前后端加密传输,前端对整个传输的body进行加密,

后端接口接收的是一个User对象,包含username、password,

个人预期是在aop内将其解密成user对象,然后controller就可以正常接收,但是请求根本发送不出去:Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of com.lin.me.entity.User (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('IZlPsCd2qVBL0hGVTzb23owpvyXa5xom1z5YXBCbrdlUXS...................................................

意思是必须要接收一个User对象 请问大家怎么解决

确实,传送明文数据就像把数据暴露给所有人一样,因此为了数据安全,加密是必不可少的。

要实现上面的功能,即在Spring Boot应用中通过AOP(面向切面编程)对前端使用RSA加密的请求体进行解密,然后将解密后的数据自动转换为User对象供Controller方法使用,需要几个步骤来完成。下面是一个简化的示例,帮助您理解如何实现这一流程。

步骤 1: 添加依赖

确保您的项目中包含了Spring AOP和RSA加密相关的依赖。如果是Maven项目,可以在pom.xml中添加如下依赖:

XML 复制代码
<!-- Spring AOP -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- RSA 加密库,这里以Bouncy Castle为例 -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>

步骤 2: 定义User类

假设您已有User类定义,大致如下:

java 复制代码
public class User {
    private String username;
    private String password;

    // getters and setters
}

步骤 3: 实现RSA加解密工具类

创建一个工具类用于RSA的加解密操作。这里简化处理,实际应用中应更安全地管理密钥。

java 复制代码
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.*;
import javax.crypto.Cipher;

public class RSAUtil {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static byte[] encrypt(byte[] data, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    public static byte[] decrypt(byte[] encryptedData, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(encryptedData);
    }
}

步骤 4: 创建AOP切面进行解密

创建一个AOP切面,用于拦截Controller中的方法,并对加密的请求体进行解密。

java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

@Aspect
@Component
public class DecryptRequestBodyAspect {

    @Around("@annotation(decryptRequestBody)")
    public Object decryptRequestBody(ProceedingJoinPoint joinPoint, DecryptRequestBody decryptRequestBody) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        String requestBody = readRequestBody(request);
        if (requestBody != null) {
            requestBody = decrypt(requestBody); // 假设decrypt方法实现了从字符串解密到User对象的逻辑
            // 将解密后的请求体设置回请求中(如果需要)
            // request.setAttribute("BODY", requestBody);
        }

        return joinPoint.proceed();
    }

    private String readRequestBody(HttpServletRequest request) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()))) {
            char[] buff = new char[1024];
            int len;
            while ((len = reader.read(buff)) != -1) {
                sb.append(buff, 0, len);
            }
        }
        return sb.toString();
    }

    // 假设的解密方法,实际需要根据加密方式实现
    private String decrypt(String encryptedBody) {
        // 这里需要实现从加密字符串到User对象的具体逻辑
        // 包括从请求中获取或配置中读取私钥等操作
        return ""; // 返回解密后的User对象的JSON字符串或其他形式
    }
}

步骤 5: 自定义注解标记需要解密的方法

创建一个自定义注解,用于标记那些需要解密请求体的方法。

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptRequestBody {
}

步骤 6: 在Controller中使用

最后,在您的Controller方法上使用这个自定义注解,以指示AOP切面进行解密操作。

java 复制代码
@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping("/login")
    @DecryptRequestBody
    public ResponseEntity<?> login(@RequestBody User user) {
        // 这时user对象应该是解密后的结果
        // 实现登录逻辑...
    }
}

请注意,这里的实现非常简化,实际应用中还需要考虑加密算法的安全性、异常处理、性能优化(如加密大文本时可能需要分块加密和解密)、私钥的安全存储等问题。同时,直接在请求体中加密整个User对象(特别是密码)可能不是最佳实践,通常密码应该单独加密处理,且不建议明文传输密码。

步骤 7: 前端js加密

在前端使用RSA加密,一个常用的库是jsencrypt,它提供了简单的API来实现RSA公钥加密。以下是如何使用jsencrypt库进行RSA加密的示例代码:

首先,确保你已经在HTML文件中引入了jsencrypt.js库。你可以通过CDN或者下载到本地项目中引用。

javascript 复制代码
<script src="https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.3.2/jsencrypt.min.js"></script>

然后在JavaScript中使用以下代码进行加密:

javascript 复制代码
// 假设后端已经提供了公钥,并且你已将其存储在变量publicKey中
const publicKey = "-----BEGIN PUBLIC KEY-----\nYOUR_PUBLIC_KEY_HERE\n-----END PUBLIC KEY-----";

// 创建JSEncrypt实例
const encryptor = new JSEncrypt();

// 设置公钥
encryptor.setPublicKey(publicKey);

// 需要加密的数据,例如用户密码
const plaintext = "your_plain_text_here";

// 执行加密操作
const ciphertext = encryptor.encrypt(plaintext);

console.log("Encrypted Text:", ciphertext);

请将YOUR_PUBLIC_KEY_HERE替换为实际的公钥字符串。这段代码会使用公钥对plaintext中的数据进行加密,并将加密后的结果输出到控制台。

请知悉,RSA加密适合加密小块数据,如密码或一些敏感信息的摘要,因为加密大量数据可能会导致性能问题。对于大块数据,可以考虑结合对称加密算法(如AES)和RSA来加密数据和密钥。

后端公钥和私钥生成类GenerateRSAKeys:

java 复制代码
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;

public class GenerateRSAKeys {

    public static void main(String[] args) throws Exception {
        // 获取KeyPairGenerator实例
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        
        // 初始化密钥对生成器,可以指定密钥长度,例如2048位
        keyPairGenerator.initialize(2048);
        
        // 生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        
        // 获取公钥和私钥
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        
        // 打印公钥和私钥(这里为了演示,简单打印其Base64编码形式)
        System.out.println("Public Key: " + Base64.getEncoder().encodeToString(publicKey.getEncoded()));
        System.out.println("Private Key: " + Base64.getEncoder().encodeToString(privateKey.getEncoded()));
    }
}

后端加解密工具类RSAUtil:

java 复制代码
package com.example.default_setting.util;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import javax.crypto.Cipher;

public class RSAUtil {
	// 示例RSA密钥,实际使用时请从安全的地方加载
	private static final String PUBLIC_KEY = "YOUR_PUBLIC_KEY";
	private static final String PRIVATE_KEY = "YOUR_PRIVATE_KEY";

	// 初始化RSA密钥对
	private static PublicKey publicKey = null;
	private static PrivateKey privateKey = null;

	static {
		Security.addProvider(new BouncyCastleProvider());
		init();
	}
	
	public static void init() {
		try {
	        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
	        publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(PUBLIC_KEY)));
	        privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIVATE_KEY)));
	    } catch (Exception e) {
	        throw new RuntimeException("Failed to initialize RSA keys", e);
	    }
	}

    public static byte[] encrypt(byte[] data, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    public static byte[] decrypt(byte[] encryptedData, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(encryptedData);
    }
    
    public static byte[] decrypt(byte[] encryptedData) throws Exception {
    	Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
    	cipher.init(Cipher.DECRYPT_MODE, privateKey);
    	return cipher.doFinal(encryptedData);
    }
 	
 	public static void main(String[] args) {
		System.out.println(1);
	}
}

但是还是不能整体加密后给后端使用,可能的解决办法如下:

1.局部加密

2.后端全部用String接收

3.后端实体增加一个字段接收,然后再在aop界面填充

如果大家还发现更好的办法,可以评论区说一下,感谢阅读!

相关推荐
晨曦_子画2 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
假装我不帅22 分钟前
asp.net framework从webform开始创建mvc项目
后端·asp.net·mvc
南宫生25 分钟前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
神仙别闹25 分钟前
基于ASP.NET+SQL Server实现简单小说网站(包括PC版本和移动版本)
后端·asp.net
Heavydrink38 分钟前
HTTP动词与状态码
java
ktkiko1141 分钟前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
计算机-秋大田1 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue
神里大人1 小时前
idea、pycharm等软件的文件名红色怎么变绿色
java·pycharm·intellij-idea
货拉拉技术1 小时前
货拉拉-实时对账系统(算盘平台)
后端
小冉在学习1 小时前
day53 图论章节刷题Part05(并查集理论基础、寻找存在的路径)
java·算法·图论