SpringBoot整合AES+RSA加密(含前端代码)
c1osed_123 2024-06-24 16:03:02 阅读 95
目录
非对称加密和对称加密
RSA和AES简介
混合加密原因:
一、RSA工具类
二、AES工具类
三、解密工具类
四、自定义注解
五、aes加密实体类
六、加密的请求参数
七、这里使用拦截器解密
八、可以修改请求参数的request
九、RSA工具类
十、前端加密需要用到的js
十一、前端拦截器对请求统一加密
十二、为什么使用拦截器不使用aop
总结
非对称加密和对称加密
非对称加密
非对称加密算法是一种密钥的保密方法。非对称加密算法需要两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。
公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
对称加密
加密秘钥和解密秘钥是一样,当你的密钥被别人知道后,就没有秘密可言了
AES 是对称加密算法,优点:加密速度快;缺点:如果秘钥丢失,就容易解密密文,安全性相对比较差
RSA 是非对称加密算法 , 优点:安全 ;缺点:加密速度慢
RSA和AES简介
RSA
加密机制:属于非对称加密,公钥用于对数据进行加密,私钥对数据进行解密,两者不可逆。公钥和私钥是同时生成的,且一一对应。比如:A拥有公钥,B拥有公钥和私钥。A将数据通过公钥进行加密后,发送密文给B,B可以通过私钥进行解密。
AES
加密机制:属于对称加密,就是说,A用密钥对数据进行AES加密后,B用同样的密钥对密文进行AES解密。
加密思路
1:调用方先将请求参数用AES加密,再利用RSA公钥对AES的密钥值加密;
2:调用方将加密后的数据发送给服务端;
3:服务端接收到头信息里的加密串,先用RSA私钥解密出AES密钥值,再用解密出的AES密钥值解密请求参数;
4:处理完毕后,服务端再用AES密钥对响应参数加密;(本篇不涉及返回值加密,可以根据代码自己调试)
5:将加密后的结果返回给调用方。(本篇不涉及返回值解密,可以根据代码自己调试)
混合加密原因:
单纯的使用 RSA(非对称加密)方式,效率会很低,因为非对称加密解密方式虽然很保险,但是过程复杂,耗费时间长,性能不高;
RSA优势在于数据传输安全,且对于几个字节的数据,加密和解密时间基本可以忽略,所以用它非常适合加密 AES 秘钥(一般16个字节);
单纯的使用AES(对称加密)方式的话,非常不安全。这种方式使用的密钥是一个固定的密钥,客户端和服务端是一样的,一旦密钥被人获取,那么,我们所发的每一条数据都会被都对方破解;
AES有个很大的优点,那就是加密解密效率很高,而我们传输正文数据时,正好需要这种加解密效率高的,所以这种方式适合用于传输量大的数据内容。
一、RSA工具类
package com.ruoyi.common.utils.rsa;import lombok.extern.slf4j.Slf4j;import org.apache.commons.codec.binary.Base64;import org.apache.commons.io.IOUtils;import javax.crypto.Cipher;import java.io.ByteArrayOutputStream;import java.security.*;import java.security.interfaces.RSAPrivateKey;import java.security.interfaces.RSAPublicKey;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;@Slf4jpublic class ActivityRSAUtil { /** * 字符集 */ public static String CHARSET = "UTF-8"; /** * 生成密钥对 * @param keyLength 密钥长度 * @return KeyPair */ public static KeyPair getKeyPair(int keyLength) { try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); //默认:RSA/None/PKCS1Padding keyPairGenerator.initialize(keyLength); KeyPair keyPair = keyPairGenerator.generateKeyPair(); log.info("私钥:{}" ,getPrivateKeyString(keyPair)); log.info("公钥:{}" ,getPublicKeyString(keyPair)); return keyPair; } catch (NoSuchAlgorithmException e) { throw new RuntimeException("生成密钥对时遇到异常" + e.getMessage()); } } /** * 获取公钥 */ public static byte[] getPublicKey(KeyPair keyPair) { RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); return rsaPublicKey.getEncoded(); } /** * 获取私钥 */ public static byte[] getPrivateKey(KeyPair keyPair) { RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); return rsaPrivateKey.getEncoded(); } /** * 公钥字符串转PublicKey实例 * @param publicKey 公钥字符串 * @return PublicKey * @throws Exception e */ public static PublicKey getPublicKey(String publicKey) throws Exception { byte[] publicKeyBytes = Base64.decodeBase64(publicKey.getBytes()); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePublic(keySpec); } /** * 私钥字符串转PrivateKey实例 * @param privateKey 私钥字符串 * @return PrivateKey * @throws Exception e */ public static PrivateKey getPrivateKey(String privateKey) throws Exception { byte[] privateKeyBytes = Base64.decodeBase64(privateKey.getBytes()); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePrivate(keySpec); } /** * 获取公钥字符串 * @param keyPair KeyPair * @return 公钥字符串 */ public static String getPublicKeyString(KeyPair keyPair){ RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公钥 String s = new String(publicKey.getEncoded()); log.info("不编码的数据:{}",s); return new String(org.apache.commons.codec.binary.Base64.encodeBase64(publicKey.getEncoded())); } /** * 获取私钥字符串 * @param keyPair KeyPair * @return 私钥字符串 */ public static String getPrivateKeyString(KeyPair keyPair){ RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私钥 return new String(org.apache.commons.codec.binary.Base64.encodeBase64((privateKey.getEncoded()))); } /** * 公钥加密 * @param data 明文 * @param publicKey 公钥 * @return 密文 */ public static String publicEncrypt(String data, RSAPublicKey publicKey) { try { Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()); return new String(org.apache.commons.codec.binary.Base64.encodeBase64(bytes)); } catch (Exception e) { throw new RuntimeException("加密字符串[" + data + "]时遇到异常"+ e.getMessage()); } } /** * 私钥解密 * @param data 密文 * @param privateKey 私钥 * @return 明文 */ public static String privateDecrypt(String data, RSAPrivateKey privateKey) { try { Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, privateKey); String str = new String(Base64.decodeBase64(data), "UTF-8"); log.info("Base64,{}",str); return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), privateKey.getModulus().bitLength()), CHARSET); } catch (Exception e) { throw new RuntimeException("privateKey解密字符串[" + data + "]时遇到异常"+ e.getMessage()); } } /** * 私钥加密 * @param content 明文 * @param privateKey 私钥 * @return 密文 */ public static String encryptByPrivateKey(String content, RSAPrivateKey privateKey){ try { Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE,content.getBytes(CHARSET), privateKey.getModulus().bitLength()); return new String(org.apache.commons.codec.binary.Base64.encodeBase64(bytes)); } catch (Exception e) { throw new RuntimeException("privateKey加密字符串[" + content + "]时遇到异常" + e.getMessage()); } } /** * 公钥解密 * @param content 密文 * @param publicKey 私钥 * @return 明文 */ public static String decryByPublicKey(String content, RSAPublicKey publicKey){ try { Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, publicKey); log.info("Base64.getDecoder().decode(content).toString(),{}",Base64.decodeBase64(content).toString()); return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(content), publicKey.getModulus().bitLength()), CHARSET); } catch (Exception e) { throw new RuntimeException("publicKey解密字符串[" + content + "]时遇到异常" +e.getMessage()); } } public static RSAPublicKey getRSAPublicKeyByString(String publicKey){ try {// org.apache.commons.codec.binary.Base64.decodeBase64(publicKeyString) X509EncodedKeySpec keySpec = new X509EncodedKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64(publicKey)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return (RSAPublicKey)keyFactory.generatePublic(keySpec); } catch (Exception e) { throw new RuntimeException("String转PublicKey出错" + e.getMessage()); } }// public static RSAPrivateKey getRSAPrivateKeyByString(String privateKey){ try { PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return (RSAPrivateKey)keyFactory.generatePrivate(pkcs8EncodedKeySpec); } catch (Exception e) { throw new RuntimeException("String转PrivateKey出错" + e.getMessage()); } } //rsa切割解码 , ENCRYPT_MODE,加密数据 ,DECRYPT_MODE,解密数据 private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) { int maxBlock = 0; //最大块 if (opmode == Cipher.DECRYPT_MODE) { maxBlock = keySize / 8; } else { maxBlock = keySize / 8 - 11; } ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] buff; int i = 0; try { while (datas.length > offSet) { if (datas.length - offSet > maxBlock) { //可以调用以下的doFinal()方法完成加密或解密数据: buff = cipher.doFinal(datas, offSet, maxBlock); } else { buff = cipher.doFinal(datas, offSet, datas.length - offSet); } out.write(buff, 0, buff.length); i++; offSet = i * maxBlock; } } catch (Exception e) { throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常: " + e.getMessage()); } byte[] resultDatas = out.toByteArray(); IOUtils.closeQuietly(out); return resultDatas; } public static void main(String[] args) {// getKeyPair(1024);//////// RSAPublicKey rsaPublicKeyByString = getRSAPublicKeyByString(RequestDecryptionUtil.publicKey);//// String aa = publicEncrypt("{\"key\":\"eViXqtVyTMPRpWVl\",\"keyVI\":\"QH9hKMwqHpcTL5Fm\",\"time\":1716531679199}", rsaPublicKeyByString);//// System.out.printf(aa);// RSAPrivateKey rsaPrivateKeyByString = getRSAPrivateKeyByString(RequestDecryptionUtil.privateKey); String s = privateDecrypt(aa, rsaPrivateKeyByString); System.out.printf(s);//try { s = RSAUtils.encryptByPublicKey(RequestDecryptionUtil.publicKey, "123"); System.out.println(s); } catch (Exception e) { throw new RuntimeException(e); }// try {// String s1 = RSAUtils.decryptByPrivateKey(RequestDecryptionUtil.privateKey, aa);// System.out.println(s1);// } catch (Exception e) {// throw new RuntimeException(e);// }////////// String s = null;// try {// s = RSAUtils.encryptByPublicKey(RequestDecryptionUtil.publicKey, "123");// System.out.println(s);// } catch (Exception e) {// throw new RuntimeException(e);// }// RSAPrivateKey rsaPrivateKeyByString = getRSAPrivateKeyByString(RequestDecryptionUtil.privateKey);//// String b = privateDecrypt(s, rsaPrivateKeyByString);//// System.out.printf(b); }}
二、AES工具类
package com.ruoyi.common.utils.rsa;import lombok.Data;import org.bouncycastle.jce.provider.BouncyCastleProvider;import java.nio.charset.StandardCharsets;import java.security.Security;import java.util.Base64;@Datapublic class AES256Util { private static final String AES = "AES"; /** * 初始向量IV, 初始向量IV的长度规定为128位16个字节, 初始向量的来源为随机生成. */ /**"AES/CBC/NoPadding" * 加密解密算法/加密模式/填充方式 */ private static final String CIPHER_ALGORITHM = "AES/CBC/NoPadding"; private static final Base64.Encoder base64Encoder = java.util.Base64.getEncoder(); private static final Base64.Decoder base64Decoder = java.util.Base64.getDecoder(); /** * key的长度,Wrong key size: must be equal to 128, 192 or 256 * 传入时需要16、24、36 */ private static final int KEY_LENGTH = 16 * 8; //通过在运行环境中设置以下属性启用AES-256支持 static { Security.setProperty("crypto.policy", "unlimited"); } /* * 解决java不支持AES/CBC/PKCS7Padding模式解密 */ static { Security.addProvider(new BouncyCastleProvider()); }// /**// * AES加密// */// public static String encode(String key, String content,String keyVI) {// try {// javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES);// javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);// cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(keyVI.getBytes()));// // 获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码// byte[] byteEncode = content.getBytes(java.nio.charset.StandardCharsets.UTF_8);// // 根据密码器的初始化方式加密// byte[] byteAES = cipher.doFinal(byteEncode);// // 将加密后的数据转换为字符串// return base64Encoder.encodeToString(byteAES);// } catch (Exception e) {// e.printStackTrace();// }// return null;// }////// /**// * 获取key// */// public static String getKey() {// int length = KEY_LENGTH / 8;// StringBuilder uid = new StringBuilder(length);// //产生16位的强随机数// Random rd = new SecureRandom();// for (int i = 0; i < length; i++) {// //产生0-2的3位随机数// switch (rd.nextInt(3)) {// case 0:// //0-9的随机数// uid.append(rd.nextInt(10));// break;// case 1:// //ASCII在65-90之间为大写,获取大写随机// uid.append((char) (rd.nextInt(26) + 65));// break;// case 2:// //ASCII在97-122之间为小写,获取小写随机// uid.append((char) (rd.nextInt(26) + 97));// break;// default:// break;// }// }// return uid.toString();// } /** * AES解密 */ public static String decode(String key, String content,String keyVI) { try { javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES); javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(keyVI.getBytes(StandardCharsets.UTF_8))); // 将加密并编码后的内容解码成字节数组 byte[] byteContent = org.apache.commons.codec.binary.Base64.decodeBase64(content.getBytes());; // 解密 byte[] byteDecode = cipher.doFinal(byteContent); return new String(byteDecode); } catch (Exception e) { e.printStackTrace(); } return null; }//这个是正确的// public static String decode(String key, String content,String keyVI){// try {// SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");// Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");// cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(keyVI.getBytes(StandardCharsets.UTF_8)));// byte[] original = cipher.doFinal(Base64.getDecoder().decode(content));// return new String(original, StandardCharsets.UTF_8);// } catch (Exception e) {// e.printStackTrace();// }// return null;// }// /**// * AES加密ECB模式PKCS7Padding填充方式// * @param str 字符串// * @param key 密钥// * @return 加密字符串// * @throws Exception 异常信息// */// public static String aes256ECBPkcs7PaddingEncrypt(String str, String key) throws Exception {// Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");// byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);// cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, AES));// byte[] doFinal = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));// return new String(Base64.getEncoder().encode(doFinal));// }//// /**// * AES解密ECB模式PKCS7Padding填充方式// * @param str 字符串// * @param key 密钥// * @return 解密字符串// * @throws Exception 异常信息// */// public static String aes256ECBPkcs7PaddingDecrypt(String str, String key) throws Exception {// Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");// byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);// cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, AES));// byte[] doFinal = cipher.doFinal(Base64.getDecoder().decode(str));// return new String(doFinal);// } public static void main(String[] args) {// String qw = encode("8L9UjJ3ImkkKoCSy", "y8CRKjEfWFxFN2or", "y8CRKjEfWFxFN2or");// System.out.println(qw);//// String qw1 = decode("8L9UjJ3ImkkKoCSy", qw, "y8CRKjEfWFxFN2or");// System.out.println(qw1); }}
三、解密工具类
package com.ruoyi.common.utils.rsa;import com.alibaba.fastjson.JSONObject;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import javax.annotation.Resource;import java.security.interfaces.RSAPrivateKey;import java.util.Objects;@Componentpublic class RequestDecryptionUtil { private static RequestDecryptionUtil requestDecryptionUtil; @Resource private ConfigurationInfo appConfig; @PostConstruct public void init() { requestDecryptionUtil = this; requestDecryptionUtil.appConfig = this.appConfig; }// public final static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDG3U7/0X2XOkSijPjCCFjOX2hGNb/BOW4Asx6S9Lx84ovzeFzFKy1rOLH2EpEQwjpC958Xro4neWpzXXIn8Fahun1B2qK2wggUsA3ylxpCxI53lwXvvC6rN6IU83MueInAwhVjIpqj/evf5LsZ9yp63z1wXVO7VmGYGb+kd6jOAwIDAQAB";// public final static String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMbdTv/RfZc6RKKM+MIIWM5faEY1v8E5bgCzHpL0vHzii/N4XMUrLWs4sfYSkRDCOkL3nxeujid5anNdcifwVqG6fUHaorbCCBSwDfKXGkLEjneXBe+8Lqs3ohTzcy54icDCFWMimqP969/kuxn3KnrfPXBdU7tWYZgZv6R3qM4DAgMBAAECgYEAomNdezCKKc9+9G3BRFCklAD8mTiS2SoYVaHuuXn34NLuDWaf+rGDaSbmy1Xl95VjFgQ2eZQkqL9Q2dvYuBxd4EiNDngkBMfFSmN5z9LqIeE8BAeUSoIfhCaXE5QEwL8765tHitveapmKmOx8NMt4HSUw6iMlimW+E+3qypL/HAECQQD5zGsshI1jN1QvlBCpJjabC5Xlf+HxSWqOZ67AQzRfKccKp8XU8/nnzrUac5xUxwi5c9NfqXuuxxYrRCda3oqDAkEAy80ucbexMBREZF3amPfBe737X7QBP3jdNYt7D5ObP+iU+jYnDh5Wstau4TdQ5kXwBunvP+j7sUiHCl4OvH9WgQJAQGpaaMx1uVQXPX2tHjFge3LtYJUtqo8ID1jlU2cBJlkbnr/M4DFaDFDdmsidU69PrKMVquGFp3hnWxjkHSauCQJAW0Evd6nZw/5/NTW1KONfFmpWAV9XY7VZz5z56FqenHonIvZWfILnLULloWCkb8eHF4FuKH7JHHOuS90b2hlmgQJAV/BWjPOD3x25AA3+2iIKM2BFCWHPOECncxNyNzG3j/D6iVQEytfqMSX7kDOrTL+cNp+GvaNm2eKKlojipOT7Mg==";//// public final static String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxEEl/TfgaYVGF0CtsPX\n" +// "Ylx3ksNZe52DJROhRoQMaRB665ozh7KtDx79zXZL47vfvh0ivlWcMPgqBmiz/s3i\n" +// "AWmWOVd6aTUmkkDC4hAWSpkk01o5Pi+c6YtGFe1uJv+4vV9EBpW2vN2Lgg/+kAc+\n" +// "4Vx1cD5FBvJdKaMsG6ei9hRl56VshW8xqSU28DNfhl/kWjZ12S20ZwTDjUP5YhfF\n" +// "2OYaFLYxING86EX/Nh0RcKoQqqlDwAIxgUJx9uhMziUfwaj1oX4PgAdGkJS1VK7k\n" +// "rZZfde7Vc6Gi6kuaDOZmtgjWM1KpJP+fnKHj9mDnDN3joAjhCwWdtTldyWDm35mJ\n" +// "PQIDAQAB";// public final static String privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPEQSX9N+BphUY\n" +// "XQK2w9diXHeSw1l7nYMlE6FGhAxpEHrrmjOHsq0PHv3Ndkvju9++HSK+VZww+CoG\n" +// "aLP+zeIBaZY5V3ppNSaSQMLiEBZKmSTTWjk+L5zpi0YV7W4m/7i9X0QGlba83YuC\n" +// "D/6QBz7hXHVwPkUG8l0poywbp6L2FGXnpWyFbzGpJTbwM1+GX+RaNnXZLbRnBMON\n" +// "Q/liF8XY5hoUtjEg0bzoRf82HRFwqhCqqUPAAjGBQnH26EzOJR/BqPWhfg+AB0aQ\n" +// "lLVUruStll917tVzoaLqS5oM5ma2CNYzUqkk/5+coeP2YOcM3eOgCOELBZ21OV3J\n" +// "YObfmYk9AgMBAAECggEAR0KaDBmPmbSoadwIhRFc3FLqK63i67HHYkkhJX1oL/gl\n" +// "9VL6DOcmu590xPLDJzqOw4SPYmVf/VJKVC5QU45TCx1lP5KlY/OQtKBo8ReMNFwD\n" +// "2mCgdpA4Nf9iPUWatP0ofLN+W63GV7T+v+H1P2fe8fu5xskHRF7ARQYMte+5G2pG\n" +// "rXrxG0I0ovAX6SRtVPYJ7d1VjNzK7TaPLTVPsX8aRK1ZTCygsBdfbECn9c7yfvov\n" +// "BtknrHEKVBK1oCXfJsjVpHLUXChKBB7K8Ozj04JugqV+d7scVOpKph+vft1RDhel\n" +// "fdLocQ2ifsNh7CKJjKbRLj5GW7AQUG89Yqfx849NWQKBgQD6AlUNrnDzLqD6RQuh\n" +// "6NJK3ab6c49FK2HOG166naNIJJx8KCeGy7ocWXkgyl5Wtf+MCB9WnP4PWb+uC4iO\n" +// "kUpUHkPmUQn3j7efxV1h6B/KhMHlmeGfJbNz37HaNUkl50RAVTRlO/GEE/1RlVza\n" +// "HyXFXgeujUC8/ILgy16+DYX2VwKBgQDUB0GhfOZ/+xwex6n4qvoCgLkfV4SDJEhx\n" +// "9oJ2a9f+FNEesKCW5GF6sllopHb3xfDYkl+g19J0aQ9J0sWQvMCvM7M7rwTPlfOh\n" +// "zYLGlQJmlO60r9WvQWRwR5JmB9J3I1gaChAelxp2ScMkcXI47rbCDoZa2q4Ej5Vk\n" +// "YqwvADx4iwKBgQCdLDQuarllmK3pSNj8S/NQz7I3B8lNUe2l9n0CUzIgm6upPlFD\n" +// "I/b39aP54l+WocprTXvuJrpuCh1AHM4X2u1gnrpPJClg8oAdOKXxdE3wMq/3WVuH\n" +// "gtsjgME+DnvTEWZOD4LmFd5LC4oY7Q63rhc/0lKAARtu9EyPaCtCzhum9wKBgBrR\n" +// "+ClXRUj3GK2EECoWZp2ebsxaI7b7Bfb0ebhFGANZ2sIJEadEqFf+63RjKXFaJoce\n" +// "rN4JruNuzrJF6RvP5IfFAG0STId9rl3PQzWfb7hOKovMmjkbCntxckFZx/OuEtzo\n" +// "XPWho4VG+1pGx24QNCoD8FbZxp1pFDqoiKNBYmTVAoGBANKtrCkWvoxLuwUj6hhG\n" +// "ml+I4uweiOBt412GOPTsWkCRXmanWAsIm7FsGPtZHCz3b9OkGZNBzGtpGSPfXzog\n" +// "NdEQfyCDuAKNT4LlVZBPDjazA3aoijaC+4jt+TQP0DsQY9k+y/IL9kx/tvZs4NBy\n" +// "KJS0DIosnuo2hl51F4b9o/o5"; private String privateKey; private final static Integer timeout = 60000; /** * * @param sym RSA 密文 * @param asy AES 密文 * @param clazz 接口入参类 * @return Object */ public static <T> Object getRequestDecryption(String sym, String asy, Class<T> clazz){ //验证密钥 try { //解密RSA RSAPrivateKey rsaPrivateKey = ActivityRSAUtil.getRSAPrivateKeyByString(requestDecryptionUtil.appConfig.getPrivateKey()); String RSAJson = ActivityRSAUtil.privateDecrypt(sym, rsaPrivateKey); RSADecodeData rsaDecodeData = JSONObject.parseObject(RSAJson, RSADecodeData.class); boolean isTimeout = Objects.nonNull(rsaDecodeData) && Objects.nonNull(rsaDecodeData.getTime()) && System.currentTimeMillis() - rsaDecodeData.getTime() < timeout; if (!isTimeout){ throw new RuntimeException("Request timed out, please try again."); //请求超时 } //解密AES String AESJson = AES256Util.decode(rsaDecodeData.getKey(),asy,rsaDecodeData.getKeyVI()); System.out.println("AESJson: "+AESJson); return JSONObject.parseObject(AESJson,clazz); } catch (Exception e) { throw new RuntimeException("RSA decryption Exception: " +e.getMessage()); } } public static JSONObject getRequestDecryption(String sym, String asy){ //验证密钥 try { //解密RSA RSAPrivateKey rsaPrivateKey = ActivityRSAUtil.getRSAPrivateKeyByString(requestDecryptionUtil.appConfig.getPrivateKey()); String RSAJson = ActivityRSAUtil.privateDecrypt(sym, rsaPrivateKey); RSADecodeData rsaDecodeData = JSONObject.parseObject(RSAJson, RSADecodeData.class);// boolean isTimeout = Objects.nonNull(rsaDecodeData) && Objects.nonNull(rsaDecodeData.getTime()) && System.currentTimeMillis() - rsaDecodeData.getTime() < timeout;// if (!isTimeout){// throw new RuntimeException("Request timed out, please try again."); //请求超时// } //解密AES String AESJson = AES256Util.decode(rsaDecodeData.getKey(),asy,rsaDecodeData.getKeyVI()); System.out.println("AESJson: "+AESJson); return JSONObject.parseObject(AESJson); } catch (Exception e) { throw new RuntimeException("RSA decryption Exception: " +e.getMessage()); } }}
四、自定义注解
package com.ruoyi.common.utils.rsa;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RequestRSA {}
五、aes加密实体类
package com.ruoyi.common.utils.rsa;import lombok.Data;@Datapublic class RSADecodeData { private String key; private String keyVI; private Long time;}
六、加密的请求参数
package com.ruoyi.common.utils.rsa;import lombok.Data;@Datapublic class RSAEncodeData { private String asy; private String sym;}
七、这里使用拦截器解密
package com.ruoyi.common.utils.rsa;import com.alibaba.fastjson.JSONObject;import com.fasterxml.jackson.databind.ObjectMapper;import com.ruoyi.common.filter.RepeatedlyRequestWrapper;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import org.springframework.util.StreamUtils;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Method;import java.util.Map;import java.util.Objects;@Slf4j@Componentpublic class RSAModuleInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); RequestRSA annotation = method.getAnnotation(RequestRSA.class); if (Objects.nonNull(annotation)) { byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream()); String body = new String(bodyBytes, request.getCharacterEncoding()); log.info("[ WebInterceptor ] >> preHandle requestUrI:{} requestBody:{}", request.getRequestURI(), body); JSONObject jsonObject = JSONObject.parseObject(body); String asy = jsonObject.get("asy").toString(); String sym = jsonObject.get("sym").toString(); JSONObject decryption = RequestDecryptionUtil.getRequestDecryption(sym, asy);// Class<?>[] parameterTypes = method.getParameterTypes();// // 打印方法参数类型// for (Class<?> parameterType : parameterTypes) {// System.out.println(parameterType.getName());// Class<? extends Class> aClass = parameterType.getClass(); Object o = JSONObject.parseObject(decryption.toJSONString(), aClass);// log.info("[ 111111111 ] >> preHandle requestUrI:{} ", aClass);////// } ObjectMapper objectMapper = new ObjectMapper(); // JSONObject转Map<String, Object> Map<String, Object> map = (Map<String, Object>)decryption.getInnerMap();// Set<Map.Entry<String, Object>> entries = decryption.entrySet();// Map<String,Object> map = new HashMap<>();// map.put("username","admin"); String s = objectMapper.writeValueAsString(map); ((RepeatedlyRequestWrapper) request).setBody(s.getBytes()); } return true; } return true; }// List<Object> argList = new ArrayList<>();// Parameter[] parameters = method.getParameters();// for (int i = 0; i < parameters.length; i++) {// //将RequestBody注解修饰的参数作为请求参数 RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); if (requestBody != null) { argList.add(args[i]); }// argList.add(args[i]);// }// if (argList.size() == 0) {// return null;// } else if (argList.size() == 1) {// return argList.get(0);// } else {// return argList;// }}
八、可以修改请求参数的request
package com.ruoyi.common.filter;import com.ruoyi.common.utils.http.HttpHelper;import javax.servlet.ReadListener;import javax.servlet.ServletInputStream;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import java.io.BufferedReader;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStreamReader;/** * 构建可重复读取inputStream的request * * @author ruoyi */public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper{ private byte[] body; public void setBody(byte[] body){ this.body = body; } public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException { super(request); request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); body = HttpHelper.getBodyString(request).getBytes("UTF-8"); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public int available() throws IOException { return body.length; } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; }}
九、RSA工具类
package com.ruoyi.common.utils.rsa;import org.apache.commons.codec.binary.Base64;import org.springframework.context.annotation.Bean;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;public class RSAUtils { // Rsa 私钥 也可固定秘钥对 若依原写法(不安全) public static String privateKeys = ""; private static String publicKeyStr = ""; private static String privateKeyStr = ""; private static final RSAKeyPair rsaKeyPair = new 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); } public static RSAPublicKey getRSAPublicKeyByString(String publicKey){ try { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(java.util.Base64.getDecoder().decode(publicKey)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return (RSAPublicKey)keyFactory.generatePublic(keySpec); } catch (Exception e) { throw new RuntimeException("String转PublicKey出错" + e.getMessage()); } } /** * 构建RSA密钥对 * * @return 生成后的公私钥信息 */ @Bean public void generateKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException { 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); publicKeyStr = publicKeyString; privateKeyStr = privateKeyString; } public static String getPublicKey() { return publicKeyStr; } public static String getPrivateKey() { return privateKeyStr; } public static RSAKeyPair rsaKeyPair() { return rsaKeyPair; } /** * RSA密钥对对象 */ public static class RSAKeyPair { private String publicKey; private String privateKey; public void setPublicKey(String publicKey) { this.publicKey = publicKey; } public void setPrivateKey(String privateKey) { this.privateKey = privateKey; } public RSAKeyPair() { } public RSAKeyPair(String publicKey, String privateKey) { this.publicKey = publicKey; this.privateKey = privateKey; } public String getPublicKey() { return publicKey; } public String getPrivateKey() { return privateKey; } } public static void main(String[] args) {// String s = null;// try {// s = encryptByPublicKey(RequestDecryptionUtil.publicKey, "123");// System.out.println(s);// } catch (Exception e) {// throw new RuntimeException(e);// }// try {// String s1 = decryptByPrivateKey(RequestDecryptionUtil.privateKey, s);// System.out.println(s1);// } catch (Exception e) {// throw new RuntimeException(e);// } }}
十、前端加密需要用到的js
import JSEncrypt from 'jsencrypt'// import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'import CryptoJS from 'crypto-js'import de from "element-ui/src/locale/lang/de";const Base64 = require("js-base64").Base64const publicKey= process.env.VUE_APP_PUBLICKEY;export function rsaEncrypt(txt) { const encryptor = new JSEncrypt() encryptor.setPublicKey(publicKey) // 设置公钥 return encryptor.encrypt(txt) // 对数据进行加密}/* * AES加密 :字符串 key iv 返回base64 *///加密方法export function aesEncrypt(word, key, iv) { const data = JSON.stringify(word); const srcs = CryptoJS.enc.Utf8.parse(data); // /** // * CipherOption, 加密的一些选项: // * mode: 加密模式, 可取值(CBC, CFB, CTR, CTRGladman, OFB, ECB), 都在 CryptoJS.mode 对象下 // * padding: 填充方式, 可取值(Pkcs7, AnsiX923, Iso10126, Iso97971, ZeroPadding, NoPadding), 都在 CryptoJS.pad 对象下 // * iv: 偏移量, mode === ECB 时, 不需要 iv // * 返回的是一个加密对象 // */ var key = CryptoJS.enc.Utf8.parse(key); // 密钥 var message = srcs; var encrypted = CryptoJS.AES.encrypt(message, key, { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, iv: CryptoJS.enc.Utf8.parse(iv) // 初始化向量 }); return encrypted.toString();}/** * 获取16位随机码AES * @returns {string} */export function get16RandomNum() { var chars = [ '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K', 'L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z' ] var nums = '' //这个地方切记要选择16位,因为美国对密钥长度有限制,选择32位的话加解密会报错,需要根据jdk版本去修改相关jar包,有点恼火,选择16位就不用处理。 for (var i = 0; i < 16; i++) { var id = parseInt(Math.random() * 61) nums += chars[id] } return nums}
十一、前端拦截器对请求统一加密
import axios from 'axios'import { Notification, MessageBox, Message, Loading } from 'element-ui'import store from '@/store'import { getToken } from '@/utils/auth'import errorCode from '@/utils/errorCode'import { tansParams, blobValidate } from "@/utils/ruoyi";import cache from '@/plugins/cache'import { saveAs } from 'file-saver'import {aesEncrypt, get16RandomNum, rsaEncrypt} from "@/api/encipher/encipher";let downloadLoadingInstance;// 是否显示重新登录export let isRelogin = { show: false };axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'// 创建axios实例const service = axios.create({ // axios中请求配置有baseURL选项,表示请求URL公共部分 baseURL: process.env.VUE_APP_BASE_API, // 超时 timeout: 5 * 60 * 1000})// request拦截器service.interceptors.request.use(config => { // 是否需要设置 token const isToken = (config.headers || {}).isToken === false // 是否需要防止数据重复提交 const isRepeatSubmit = (config.headers || {}).repeatSubmit === false if (getToken() && !isToken) { config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } // get请求映射params参数 if (config.method === 'get' && config.params) { let url = config.url + '?' + tansParams(config.params); url = url.slice(0, -1); config.params = {}; config.url = url; } if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { let key = get16RandomNum(); let keyVI = get16RandomNum(); let time = Date.now(); let param = { "key":key, "keyVI":keyVI, "time":time } let data = typeof config.data === 'object' ? JSON.stringify(config.data) : config.data let asy = aesEncrypt(config.data,key,keyVI); //用登陆后后端生成并返回给前端的的RSA密钥对的公钥将AES16位密钥进行加密 let sym = rsaEncrypt(JSON.stringify(param)) console.log("加密数据") console.log(data) console.log(asy) console.log(sym) let paramData = { "asy":asy, "sym":sym } console.log(paramData) console.log(JSON.stringify(paramData)) config.data = paramData; const requestObj = { url: config.url, data: paramData, time: new Date().getTime() } console.log(requestObj) const sessionObj = cache.session.getJSON('sessionObj') if (sessionObj === undefined || sessionObj === null || sessionObj === '') { cache.session.setJSON('sessionObj', requestObj) } else { const s_url = sessionObj.url; // 请求地址 const s_data = sessionObj.data; // 请求数据 const s_time = sessionObj.time; // 请求时间 const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { const message = '数据正在处理,请勿重复提交'; console.warn(`[${s_url}]: ` + message) return Promise.reject(new Error(message)) } else { cache.session.setJSON('sessionObj', requestObj) } } } return config}, error => { console.log(error) Promise.reject(error)})// 响应拦截器service.interceptors.response.use(res => { // 未设置状态码则默认成功状态 const code = res.data.code || 200; // 获取错误信息 const msg = errorCode[code] || res.data.msg || errorCode['default'] // 二进制数据则直接返回 if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { return res.data } if (code === 401) { if (!isRelogin.show) { isRelogin.show = true; MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' } ).then(() => { isRelogin.show = false; store.dispatch('LogOut').then(() => { // 如果是登录页面不需要重新加载 if (window.location.hash.indexOf("#/login") != 0) { location.href = '/index'; } }) }).catch(() => { isRelogin.show = false; }); } return Promise.reject('无效的会话,或者会话已过期,请重新登录。') } else if (code === 500) { Message({ message: msg, type: 'error' }) return Promise.reject(new Error(msg)) } else if (code === 5000) { MessageBox.confirm(msg, '系统提示', { confirmButtonText: '确认', showCancelButton: false, type: 'warning' }) return Promise.reject(new Error(msg)) } else if (code == 7000) { return res.data }else if (code !== 200) { Notification.error({ title: msg }) return Promise.reject('error') } else { return res.data }}, error => { console.log('err' + error) let { message } = error; if (message == "Network Error") { message = "后端接口连接异常"; } else if (message.includes("timeout")) { message = "系统接口请求超时"; } else if (message.includes("Request failed with status code")) { message = "系统接口" + message.substr(message.length - 3) + "异常"; } Message({ message: message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) })// 通用下载方法export function download(url, params, filename) { downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) return service.post(url, params, { transformRequest: [(params) => { return tansParams(params) }], headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, responseType: 'blob' }).then(async (data) => { const isLogin = await blobValidate(data); if (isLogin) { const blob = new Blob([data]) saveAs(blob, filename) } else { const resText = await data.text(); const rspObj = JSON.parse(resText); const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] Message.error(errMsg); } downloadLoadingInstance.close(); }).catch((r) => { console.error(r) Message.error('下载文件出现错误,请联系管理员!') downloadLoadingInstance.close(); })}export default service
十二、为什么使用拦截器不使用aop
本来是使用aop实现解密逻辑的,但是项目中使用校验的注解,发现@Validated注解会先于aop执行参数校验,所有使用拦截器。
总结
代码全部贴出来了,不仔细去讲解内容了,直接复制,拦截器这部分需要你自己添加到自己的配置文件中(如何集成拦截器知识自己查询)。所有的方法经过测试,不明白的可以留言。
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。