实现原理:
通过过滤器和切面实现对接口的入参和出参的加解密(注:本来是想都写到过滤器里面实现,但是出参写入的时候,会提前断开,造成返回数据缺失)
代码:
结构
EncryptionFilter 过滤器
@Slf4j
public class EncryptionFilter extends OncePerRequestFilter {
private static final String PRIVATE_KEY = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDfH37gz54KUCr_85tLqTn5zlMtd1Xo0LqrDeDdupTE3fV7nqlL8S-CkMAvV17exRMqOVuSuk1QIbfUt-2ThAz6ZA1ZzQVmwe10XrjJgbe5WeywTDmxEWGn_J5eXrV8-nKojaYQdIdVBKEkPDHnhH8wB_2QBFQlQqg7AjfBNb6jrlMggK_GGNzFCqb8p2qF_WxpWjJuz7xjvzApiR6FB5d0OT4ucFjOIKkfOov1aQKjTvOEBVgsGQaJ5e20m2vRujJ34NegvrksoUekg822QJAwkDcg9hjZ4ZUqh8ERhVVhxGfUNRfkHB3QUtdGlc2EArBD-l9B4cjGZjX9kwnc-eWjAgMBAAECggEAXJmWr7iaz47r-aPhnKoT_J_rl2ACkIgoIJu26m7oFThFDFQR0Qm8_QQULtGk9vF1fb2mSqP1OD92KttWxzUzqatiexWCm136raQuLrseQ9eVSqmn-9vJ2s0V8PZ_fen3Mgrvk76498jfE8nDsGEl5Ao86stRHNGoPi0ydIZYo1cMluw1XryFIyBniz7_k3VLEFuuFiSqsM3zKvlJmo_2vPNIwjG3iTL9Qiu6hBYYb_vLHa4QQLduhFRetM3BHVyMVwqavsY-p5OMqJnOeL7c59mn7zprK8jzXjEq2uKMwUFVPCf2eCGhc67LcTR3F8nbdkP35Vv72q70VGtzsfIPgQKBgQDxmYTDkILBxA84Kxr3QGshFnfOvCwZFQfe2_df8bfVhwwS8OdAi7P2JELZI6044-9SucfUoItBlbrpikH-Sm7DtIA8nhawwYsBrEAIN9JhYGTgO49yFrgtuVCArhb4HeAvhW3etYEO2n3m1GrgrHsaxEDFzZaUmabqfy6zumEo4wKBgQDsbAxrW6jMk3eXVOFPwW05dBVQbtFfeE_EC_jW2cZRra6gNacyfed5z0bUPlC4a0sBVcEa7nzJpxV7JGy-BQ_9FALtxHrl1Jqwqf-nw-XmFqa79uUPzLHvSTDEIRTSJYleoRXT0ng3_n9-s1tZAOV1QHshNiMnPHxwx-5J57KsQQKBgQDDGE9HhRix09reZqCd7N0VmQkiXn3Vu0_hvQsj5ROoUCeF3BdB3g5yw5FfuxmUiSunR_KAVyxcx8Zp9IWaDQ0o6Edtq9Ny5LeVoD0M5dbzX13WXQJXBNWxxqWlY--UaLuyZDL21ubi0bk9f2bXCzFVygjsMjpYwUMytYVHCUiQNQKBgDFyi8psekqHUUzycMlokDqi_845z0qjdDNxuZLeK29r3wkdD6Is2RwN8Sd0_RcFQcO-tsu2M51Nd92wiZnYnzZ0WAR4wqeNJulqNNFW1J-h_y9y_Qen7VM5wJxUuvEU7r0-_by22XQEo4_RXXqpCFTUrqwMkZ-kM_a2qi0E68bBAoGBAKyKYsqXnfpRBI2WwTXmxSE-V1hOtVqH6g5fzPp8Uvc8VdIqTfLPt6X4cwaH2qSDBndJYIRY5dr7AYqVCtcc84IT69vhdzytPlwQ6pibYxclrPGIjOp75GzlM-EKSR0wg2tTSzGP-hKzNVPCIQ9Xcyh48FCPWfxQmgvmQHUoIngp";// 示例私钥
private static final String FILTERED_ATTRIBUTE = "isFiltered";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException {
//标记为通过了过滤器过滤
request.setAttribute(FILTERED_ATTRIBUTE, Boolean.TRUE);
CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(request);
// 获取请求体中的数据
String requestBody = new String(cachedRequest.getCachedBody(), StandardCharsets.UTF_8);
// 将请求体字符串转换为BaseGeneral对象
BaseGeneral baseGeneral = JSONUtil.toBean(requestBody, BaseGeneral.class);
// 使用私钥解密data
try {
String decryptedData = CryptoUtil.decrypt(baseGeneral, PRIVATE_KEY);
// 将解密后的请求体传递给过滤链
filterChain.doFilter(new CachedBodyHttpServletRequest(request, decryptedData.getBytes()), response);
} catch (Exception e) {
log.error("解密失败", e);
throw new RuntimeException(e);
}
}
}
EncryptionAspect 切面,加密处理返回数据
@Aspect
@Component
@Slf4j
public class EncryptionAspect {
private static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3x9-4M-eClAq__ObS6k5-c5TLXdV6NC6qw3g3bqUxN31e56pS_EvgpDAL1de3sUTKjlbkrpNUCG31Lftk4QM-mQNWc0FZsHtdF64yYG3uVnssEw5sRFhp_yeXl61fPpyqI2mEHSHVQShJDwx54R_MAf9kARUJUKoOwI3wTW-o65TIICvxhjcxQqm_Kdqhf1saVoybs-8Y78wKYkehQeXdDk-LnBYziCpHzqL9WkCo07zhAVYLBkGieXttJtr0boyd-DXoL65LKFHpIPNtkCQMJA3IPYY2eGVKofBEYVVYcRn1DUX5Bwd0FLXRpXNhAKwQ_pfQeHIxmY1_ZMJ3PnlowIDAQAB"; // 示例公钥
private static final String FILTERED_ATTRIBUTE = "isFiltered";
@Around("execution(* com.ugee.emp.restapi..*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request= ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
if (Boolean.TRUE.equals(request.getAttribute(FILTERED_ATTRIBUTE))) {
HttpServletResponse response = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse();
try {
// 继续执行控制器方法
Object result = joinPoint.proceed();
// 获取响应体中的数据
EpmResult epmResult = JSONUtil.toBean(result.toString(), EpmResult.class);
String data = epmResult.getDataString();
// 加密响应体
BaseGeneral encryptedData = CryptoUtil.encrypt(data, PUBLIC_KEY);
// 设置加密后的数据
epmResult.setData(encryptedData);
String jsonStr = JSONUtil.toJsonStr(epmResult);
// 设置响应内容
response.setContentType("application/json;charset=UTF-8");
try (PrintWriter writer = response.getWriter()) {
writer.write(jsonStr);
writer.flush();
}
return null; // 返回null以避免重复写入响应
} catch (Exception e) {
log.error("加密失败", e);
throw new RuntimeException(e);
}
}else {
// 如果请求没有通过过滤器,则直接执行控制器方法
return joinPoint.proceed();
}
}
}
CachedBodyHttpServletRequest 复制request,因为getInputStream()只能调用一次,request不可变,使用这个进行拷贝
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedBody = toByteArray(requestInputStream);
}
public CachedBodyHttpServletRequest(HttpServletRequest request,byte[] cachedBody) throws IOException {
super(request);
InputStream requestInputStream = new ByteArrayInputStream(cachedBody);
this.cachedBody = toByteArray(requestInputStream);
}
private byte[] toByteArray(InputStream inputStream) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return outputStream.toByteArray();
}
// 新增方法,用于获取缓存的请求体字节数组
public byte[] getCachedBody() {
// 返回拷贝以避免外部修改原始数组
return cachedBody.clone();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
// 不支持异步读取
}
};
}
}
WebConfig 添加过滤器的配置bean
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean<EncryptionFilter> encryptionFilterRegistration() {
FilterRegistrationBean<EncryptionFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new EncryptionFilter());
registration.addUrlPatterns("/crypto/*"); // 指定过滤器应用的URL模式
registration.setName("encryptionFilter");
registration.setOrder(1); // 设置过滤器的顺序
return registration;
}
}
CryptoUtil 加解密定义的工具类
/**
* 加解密工具类
*/
@Slf4j
public class CryptoUtil {
private static final String RSA = "RSA";
private static final String AES = "AES";
private static final String TRANSFORMATION_RSA = "RSA/ECB/PKCS1Padding";
private static final String TRANSFORMATION_AES = "AES/ECB/PKCS5Padding";
/**
* 生成RSA公钥和私钥字符串。
*
* @return 包含公钥和私钥字符串的类。
*/
public static RsaKeyPair generateRsaKeyStrings() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA);
keyGen.initialize(2048);
KeyPair keyPair = keyGen.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 将公钥和私钥转换为字符串形式
String publicKeyStr = Base64.getUrlEncoder().encodeToString(publicKey.getEncoded());
String privateKeyStr = Base64.getUrlEncoder().encodeToString(privateKey.getEncoded());
return new RsaKeyPair(publicKeyStr, privateKeyStr);
} catch (NoSuchAlgorithmException e) {
log.error("生成RSA公钥和私钥失败", e);
throw new RuntimeException("生成RSA公钥和私钥失败", e);
}
}
/**
* 使用公钥加密数据。
*
* @param data 要加密的数据。
* @param publicKey 公钥字符串。
* @return 加密后的数据。
*/
public static String rsaEncrypt(String data, String publicKey) throws Exception {
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.getUrlDecoder().decode(publicKey));
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(TRANSFORMATION_RSA);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return Base64.getUrlEncoder().encodeToString(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)));
}
/**
* 使用私钥解密数据。
*
* @param encryptedData 要解密的数据。
* @param privateKey 私钥字符串。
* @return 解密后的数据。
*/
public static String rsaDecrypt(String encryptedData, String privateKey) throws Exception {
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.getUrlDecoder().decode(privateKey));
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(TRANSFORMATION_RSA);
cipher.init(Cipher.DECRYPT_MODE, priKey);
// 解密数据
byte[] decryptedData = cipher.doFinal(Base64.getUrlDecoder().decode(encryptedData));
// 返回解密后的字符串
return new String(decryptedData, StandardCharsets.UTF_8);
}
/**
* 使用AES密钥加密数据。
*
* @param data 要加密的数据。
* @param key AES密钥。
* @return 加密后的数据。
*/
public static String aesEncrypt(String data, String key) throws Exception {
byte[] keyByte = Base64.getUrlDecoder().decode(key);
SecretKeySpec skeySpec = new SecretKeySpec(keyByte, AES);
Cipher cipher = Cipher.getInstance(TRANSFORMATION_AES);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
return Base64.getUrlEncoder().encodeToString(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)));
}
/**
* 使用AES密钥解密数据。
*
* @param encryptedData 要解密的数据。
* @param key AES密钥。
* @return 解密后的数据。
*/
public static String aesDecrypt(String encryptedData, String key) throws Exception {
byte[] keyByte = Base64.getUrlDecoder().decode(key);
SecretKeySpec skeySpec = new SecretKeySpec(keyByte, AES);
Cipher cipher = Cipher.getInstance(TRANSFORMATION_AES);
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
return new String(cipher.doFinal(Base64.getUrlDecoder().decode(encryptedData)), StandardCharsets.UTF_8);
}
/**
* 混合数据解密。
*/
public static String decrypt(BaseGeneral baseGeneral, String clientPirKey) throws Exception {
// 先用RSA解密AES密钥
String aesKey = rsaDecrypt(baseGeneral.getAesKey(), clientPirKey);
// 再用AES密钥解密数据
return aesDecrypt(baseGeneral.getData(), aesKey);
}
/**
* 混合数据加密。
*/
public static BaseGeneral encrypt(String jsonData, String clientPubKey) throws Exception {
// 先生成AES密钥
byte[] aesKey = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(aesKey);
String aesKeyStr = Base64.getUrlEncoder().encodeToString(aesKey);
// 再用AES加密数据串
String encryptedData = aesEncrypt(jsonData, aesKeyStr);
// 用RSA加密AES密钥
String encryptedAesKey = rsaEncrypt(aesKeyStr, clientPubKey);
return new BaseGeneral(encryptedAesKey,encryptedData);
}
}
RsaKeyPair,RSA公钥私钥对象
@Data
@AllArgsConstructor
@ToString
public class RsaKeyPair {
/**
* 公钥
*/
private String publicKey;
/**
* 私钥
*/
private String privateKey;
}
BaseGeneral,定义的加解密数据结构
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class BaseGeneral {
/**
* Rsa加密的Aes密钥
*/
private String aesKey;
/**
* Aes加密的请求实体
*/
private String data;
}