ToB企服应用市场:ToB评测及商务社交产业平台

标题: SpringBoot实现前后端传输加密设计 [打印本页]

作者: 兜兜零元    时间: 2024-9-6 03:54
标题: SpringBoot实现前后端传输加密设计
在Web应用中,确保前后端之间的数据传输安全是非常紧张的。这通常涉及到利用HTTPS协议、数据加密、令牌验证等安全措施。本文通过将前后端之间的传输数据举行加密,用于在Spring Boot应用中实现前后端传输加密设计。


一、数据加密方案

即使利用了HTTPS,也大概必要在应用层对数据举行额外的加密。这可以通过以下方式实现:

这里就赘述介绍每种加密的实现方式和原理。


1.1 数据加密实现方式

如果数据传输较大,密钥不必要举行网络传输,数据不必要很高的安全级别,则采用对称加密,只要能包管密钥没有人为外泄即可;
如果数据传输小,而且对安全级别要求高,大概密钥必要通过internet交换,则采用非对称加密;
本文采用了两者联合的方式(混淆加密模式),如许是大多数场景下采用的加密方式。加密时序图如下所示:

通过利用对称加密(AES) 和 非对称加密(RSA) 的方式来实现对数据的加密;即通过对称加密举行业务数据体的加密,通过非对称加密举行对称加密密钥的加密;它联合了对称加密的高效性 和 非对称加密的安全性。
留意事项:

这种混淆加密模式提供了安全性和服从之间的平衡。对称加密(如AES)用于加密大量数据,因为它通常比非对称加密更快。而非对称加密(如RSA)用于加密密钥,因为它提供了更强的安全性,特别是当密钥必要在不安全的通道上传输时。


1.2 AES加密工具类创建


  1. <!--   hutool-all工具类依赖   -->
  2. <dependency>
  3.   <groupId>cn.hutool</groupId>
  4.   <artifactId>hutool-all</artifactId>
  5.   <version>5.8.18</version>
  6. </dependency>
复制代码

  1. package com.example.api_security_demo.utils;
  2. import cn.hutool.core.codec.Base64;
  3. import javax.crypto.Cipher;
  4. import javax.crypto.KeyGenerator;
  5. import javax.crypto.spec.SecretKeySpec;
  6. import java.io.UnsupportedEncodingException;
  7. import java.security.Key;
  8. import java.security.NoSuchAlgorithmException;
  9. import java.security.SecureRandom;
  10. import java.util.Random;
  11. /**
  12. * @ClassName : AESUtil
  13. * @Description : AES加密工具类
  14. * @Author : AD
  15. */
  16. public class AESUtil {
  17.     public static final String CHAR_ENCODING = "UTF-8";
  18.     /**
  19.      * [常见算法]AES、DES、RSA、Blowfish、RC4 等等
  20.      * [常见的模式] ECB (电子密码本模式)、CBC (密码分组链接模式)、CTR (计数模式) 等等
  21.      * [常见的填充] NoPadding、PKCS5Padding、PKCS7Padding 等等
  22.      *
  23.      * [AES算法]可以有以下几种常见的值:
  24.      * AES:标准的AES算法。
  25.      * AES/CBC/PKCS5Padding:使用CBC模式和PKCS5填充的AES算法。
  26.      * AES/ECB/PKCS5Padding:使用ECB模式和PKCS5填充的AES算法。
  27.      * AES/GCM/NoPadding:使用GCM模式的AES算法,不需要填充。
  28.      * AES/CCM/NoPadding:使用CCM模式的AES算法,不需要填充。
  29.      * AES/CFB/NoPadding:使用CFB模式的AES算法,不需要填充。
  30.      * */
  31.     public static final String AES_ALGORITHM = "AES";
  32.     public static char[] HEXCHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
  33.     /**
  34.      * Description: 随机生成 AESKey密钥
  35.      *
  36.      * @param length 随机生成密钥长度
  37.      * @return java.lang.String
  38.     */
  39.     public static String getAESKey(int length) throws Exception
  40.     {
  41.         /*
  42.         * Random类用于生成伪随机数。
  43.         * */
  44.         Random random = new Random();
  45.         StringBuilder ret = new StringBuilder();
  46.         for(int i = 0; i < length; i++)
  47.         {
  48.             // 选择生成数字还是字符
  49.             boolean isChar = (random.nextInt(2) % 2 == 0);
  50.             /* 0随机生成一个字符*/
  51.             if (isChar)
  52.             {
  53.                 // 选择生成大写字母 / 小写字母
  54.                 int choice = (random.nextInt(2) % 2 == 0) ? 65 : 97;
  55.                 ret.append((char) (choice+random.nextInt(26)));
  56.                 /* 1随机生成一个数字 */
  57.             }else
  58.             {
  59.                 ret.append( random.nextInt(10));
  60.             }
  61.         }
  62.         return ret.toString();
  63.     }
  64.     /**
  65.      * Description: 加密
  66.      *
  67.      * @param data 待加密数据内容
  68.      * @param aesKey 加密密钥
  69.      * @return byte[]
  70.     */
  71.     public static byte[] encrypt(byte[] data,byte[] aesKey)
  72.     {
  73.         if (aesKey.length != 16)
  74.         {
  75.             throw new RuntimeException("Invalid AES key length (must be 16 bytes) !");
  76.         }
  77.         try{
  78.             /*
  79.             * 创建一个SecretKeySpec对象来包装AES密钥。
  80.             * 它使用了aesKey字节数组作为密钥,并指定算法为"AES"。
  81.             * 这个对象用来提供对称加密算法的密钥。
  82.             * */
  83.             SecretKeySpec secretKey = new SecretKeySpec(aesKey, "AES");
  84.             /*
  85.             * 获取SecretKeySpec对象中的编码形式,将其存储在encodedFormat字节数组中。
  86.             * 这个编码形式可以被用来重新构造密钥。
  87.             * */
  88.             byte[] encodedFormat = secretKey.getEncoded();
  89.             /*
  90.             * 使用encodedFormat字节数组创建了另一个SecretKeySpec对象secKey。
  91.             * 这个对象也用来提供对称加密算法的密钥。
  92.             * */
  93.             SecretKeySpec secKey = new SecretKeySpec(encodedFormat, "AES");
  94.             /*
  95.             * 使用Cipher类的getInstance()方法获取了一个Cipher对象(创建密码器)。
  96.             * 这个对象用来完成加密或解密的工作。
  97.             * */
  98.             Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
  99.             /*
  100.             * 码调用init()方法来初始化Cipher对象(初始化)。
  101.             * 它要求传入操作模式和提供密钥的对象,这里使用Cipher.ENCRYPT_MODE代表加密模式,以及之前创建的secKey对象作为密钥。
  102.             * */
  103.             cipher.init(Cipher.ENCRYPT_MODE,secKey);
  104.             /*
  105.             * 用Cipher对象对data进行加密操作,得到加密后的结果存储在result字节数组中。
  106.             * */
  107.             byte[] result = cipher.doFinal(data);
  108.             return result;
  109.         }catch (Exception e){
  110.             throw new RuntimeException(" encrypt fail! ",e);
  111.         }
  112.     }
  113.     /**
  114.      * Description: 解密
  115.      *
  116.      * @param data 解密数据
  117.      * @param aesKey 解密密钥
  118.      * @return byte[]
  119.      */
  120.     public static byte[] decrypt(byte[] data,byte[] aesKey)
  121.     {
  122.         if (aesKey.length != 16)
  123.         {
  124.             throw new RuntimeException(" Invalid AES Key length ( must be 16 bytes)");
  125.         }
  126.         try {
  127.             SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
  128.             byte[] encodedFormat = secretKeySpec.getEncoded();
  129.             SecretKeySpec secKey = new SecretKeySpec(encodedFormat, "AES");
  130.             /* 创建密码器 */
  131.             Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
  132.             /* 初始化密码器 */
  133.             cipher.init(Cipher.DECRYPT_MODE,secKey);
  134.             byte[] result = cipher.doFinal(data);
  135.             return result;
  136.         }catch (Exception e){
  137.             throw new RuntimeException(" Decrypt Fail !",e);
  138.         }
  139.     }
  140.     /**
  141.      * Description:加密数据,并转换为Base64编码格式!
  142.      *
  143.      * @param data 待加密数据
  144.      * @param aeskey 加密密钥
  145.      * @return java.lang.String
  146.     */
  147.     public static String encryptToBase64(String data,String aeskey)
  148.     {
  149.         try {
  150.             byte[] valueByte = encrypt(data.getBytes(CHAR_ENCODING), aeskey.getBytes(CHAR_ENCODING));
  151.             /* 加密数据转 Byte[]--> 换为Base64 --> String */
  152.             return Base64.encode(valueByte);
  153.         }catch (UnsupportedEncodingException e){
  154.             throw new RuntimeException(" Encrypt Fail !",e);
  155.         }
  156.     }
  157.     /**
  158.      * Description: 解密数据,将Basse64格式的加密数据进行解密操作
  159.      *
  160.      * @param data
  161.      * @param aeskey
  162.      * @return java.lang.String
  163.     */
  164.     public static String  decryptFromBase64(String data,String aeskey)
  165.     {
  166.         try {
  167.             byte[] originalData = Base64.decode(data.getBytes());
  168.             byte[] valueByte = decrypt(originalData,aeskey.getBytes(CHAR_ENCODING));
  169.             return new String(valueByte,CHAR_ENCODING);
  170.         }catch (UnsupportedEncodingException e){
  171.             throw new RuntimeException("Decrypt Fail !",e);
  172.         }
  173.     }
  174.     /**
  175.      * Description:加密数据,aesKey为Base64格式时,并将加密后的数据转换为Base64编码格式
  176.      *
  177.      * @param data
  178.      * @param aesKey
  179.      * @return java.lang.String
  180.     */
  181.     public static String encryptWithKeyBase64(String data,String aesKey)
  182.     {
  183.         try{
  184.             byte[] valueByte = encrypt(data.getBytes(CHAR_ENCODING), Base64.decode(aesKey.getBytes()));
  185.             return Base64.encode(valueByte);
  186.         }catch (UnsupportedEncodingException e){
  187.             throw new RuntimeException("Encrypt Fail!",e);
  188.         }
  189.     }
  190.     /**
  191.      * Description: 解密数据,数据源为Base64格式,且 aesKey为Base64编码格式
  192.      *
  193.      * @param data
  194.      * @param aesKey
  195.      * @return java.lang.String
  196.     */
  197.     public static String decryptWithKeyBase64(String data,String aesKey)
  198.     {
  199.         try {
  200.             byte[] originalDate = Base64.decode(data.getBytes());
  201.             byte[] valueByte = decrypt(originalDate,Base64.decode(aesKey.getBytes()));
  202.             return new String(valueByte,CHAR_ENCODING);
  203.         }catch (UnsupportedEncodingException e){
  204.             throw new RuntimeException("Decrypt Fail !",e);
  205.         }
  206.     }
  207.     /**
  208.      * Description:通过密钥生成器生成一个随机的 AES 密钥,并将其以字节数组的形式返回。
  209.      * 主要功能是生成并返回一组随机的密钥字节数组,这些字节数组可用于加密和解密数据。
  210.      *
  211.      * @param
  212.      * @return byte[]
  213.     */
  214.     public static byte[] generateRandomAesKey()
  215.     {
  216.         KeyGenerator keyGenerator = null;
  217.         try{
  218.             /*
  219.             * KeyGenerator是Java Cryptography Architecture(JCA)提供的主要密钥生成器类之一,用于生成对称加密算法的密钥。
  220.             * 获取一个用于生成AES算法密钥的KeyGenerator实例,以便在加密和解密操作中使用该密钥。
  221.             * */
  222.             keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);
  223.         }catch (NoSuchAlgorithmException e){
  224.             throw new RuntimeException("GenerateRandomKey Fail !",e);
  225.         }
  226.         /*
  227.         * SecureRandom 类提供了一种用于生成加密强随机数的实现。
  228.         * */
  229.         SecureRandom secureRandom = new SecureRandom();
  230.         /*
  231.         * 初始化密钥生成器 keyGenerator。
  232.         * 初始化密钥生成器时使用了 SecureRandom 实例,以确保生成的密钥具有足够的随机性。
  233.         * */
  234.         keyGenerator.init(secureRandom);
  235.         /*
  236.         * 调用 generateKey() 方法,使用初始化后的 keyGenerator 生成密钥对象 key。
  237.         * */
  238.         Key key = keyGenerator.generateKey();
  239.         //返回生成的密钥的字节数组表示。
  240.         return key.getEncoded();
  241.     }
  242.     /**
  243.      * Description: 通过密钥生成器生成一个随机的 AES 密钥,并转化为Base64格式
  244.      *
  245.      * @param
  246.      * @return java.lang.String
  247.     */
  248.     public static String generateRandomAesKeyWithBase64()
  249.     {
  250.         return Base64.encode(generateRandomAesKey());
  251.     }
  252. /* !!当GET请求进行加密时,地址上的加密参数就以16进制字符串的方式进行传输,否则特殊符号路径无法解析[ +、/、=]等Base64编码格式 */
  253.     /**
  254.      * Description: 从Byte[] 数组转 16进制字符串
  255.      *
  256.      * @param b
  257.      * @return java.lang.String
  258.      */
  259.     public static String toHexString(byte[] b)
  260.     {
  261.         /*
  262.          * 每个字节都可以用两个十六进制字符来表示,因此初始化的容量是字节数组长度的两倍。
  263.          * */
  264.         StringBuilder sb = new StringBuilder(b.length * 2);
  265.         for (int i = 0; i<b.length ;i++)
  266.         {
  267.             /*
  268.              * 首先取字节的高四位,然后查找对应的十六进制字符,并将其追加到StringBuilder中
  269.              * */
  270.             sb.append(HEXCHAR[(b[i] & 0xf0) >>> 4]);
  271.             /*
  272.              * 取字节的低四位,找到对应的十六进制字符,并追加到StringBuilder中。
  273.              * */
  274.             sb.append(HEXCHAR[b[i] & 0x0f]);
  275.         }
  276.         return sb.toString();
  277.     }
  278.     /**
  279.      * Description: 从16进制字符串转 byte[] 数组
  280.      *
  281.      * @param s
  282.      * @return byte[]
  283.      */
  284.     public static final byte[] toBytes(String s)
  285.     {
  286.         byte[] bytes;
  287.         bytes = new byte[s.length() / 2];
  288.         for (int i = 0; i < bytes.length ; i++)
  289.         {
  290.             bytes[i] = (byte) Integer.parseInt(s.substring(2*i,2*i+2),16);
  291.         }
  292.         return bytes;
  293.     }
  294. }
复制代码
AES加解密工具类方法代码解析(为了方便自己理解和利用,有须要简单分类记录一下工具类中的方法接口):
AESUtil工具类中的方法封装的比力紊乱,通过梳理之后更加能理清每个方法的详细用法和功能!

该工具类中总共封装了两种 生成 AES密钥的方法 String getAESKey(int length) 和 String generateRandomAesKeyWithBase64()
     


AES工具类封装的加密数据方法有以下几种  byte[] encrypt(byte[] data,byte[] aesKey)、 String encryptToBase64(String data,String aeskey) 和  String encryptWithKeyBase64(String data,String aesKey) 共三种加密方式:
     注:着实别的加密方法都是基于该方法举行封装的。也可以根据自己需求来调整,留意区别在于传入的数据格式有所区别!!
     


AES工具类中封装的解密方法,对应于加密方法:byte[] decrypt(byte[] data,byte[] aesKey)、 String decryptFromBase64(String data,String aeskey) 和  String decryptFromBase64(String data,String aeskey)。三种方式。
   该三种方式分别与上面三种加密方式是对应的。必要留意传入的数据封装格式就行。
    


该方法是将byte[] 字节数据转换为16进制的字符串数据,后续会利用到。 好比在Get请求种传输加密数据,如果前端加密后的数据必要放入地址中举行传输到后端;若采用Base64编码格式数加密数据举行传输时,加密内容会包含 +、\、= 三个符号,无法在地址中举行传输了。所有这里封装了该方法,通过调用该方法,将加密后的byte[] 字节数据数据转换为 HexString 16进制字符串格式(只包含了 0~9、a、b、c、d、e、f)。如许Get请求中的加密数据就可以通过地址举行传输了

该方法于 toHexString 方法相对应,将转换为HexString十六进制的字符串 还原为字节数据Byte[]。


1.3 RSA加密工具类创建

RSA加密工具类,同样引用了 hutool-all 依赖工具类。
  1. package com.example.api_security_demo.utils;
  2. import cn.hutool.core.codec.Base64Encoder;
  3. import javax.crypto.Cipher;
  4. import java.io.ByteArrayOutputStream;
  5. import java.security.*;
  6. import java.security.interfaces.RSAPrivateKey;
  7. import java.security.interfaces.RSAPublicKey;
  8. import java.security.spec.PKCS8EncodedKeySpec;
  9. import java.security.spec.X509EncodedKeySpec;
  10. import java.util.*;
  11. /**
  12. * @ClassName : RSAUtil
  13. * @Description : RSA加密工具类
  14. * @Author : AD
  15. */
  16. public class RSAUtil {
  17.     /**
  18.      * "SHA256withRSA" 是一种使用 SHA-256 哈希算法和 RSA 加密算法结合的数字签名算法。
  19.      * 在这种算法中,数据首先会通过 SHA-256 进行哈希处理,得到一个固定长度的摘要,然后使用 RSA 私钥对这个摘要进行加密,从而生成数字签名。
  20.      * */
  21.     public static final String  ALGORITHM_SHA256WITHRSA = "SHA256withRSA";
  22.     public static final String KEY_ALGORITHM ="RSA";
  23.     //RSA最大加密明文大小
  24.     public static final int MAX_ENCRYPT_BLOCK = 117;
  25.     //RSA最大解密密文大小
  26.     public static final int MAX_DECRYPT_BLOCK = 128;
  27.     private static char[] HEXCHAR = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
  28.     /**
  29.      * Description: 公钥分段加密
  30.      *
  31.      * @param data 待加密源数据
  32.      * @param publicKey 公钥(BASE64编码)
  33.      * @param length 段长 1024长度的公钥最大取117
  34.      * @return byte[]
  35.     */
  36.     public static byte[] encryptByPublicKey(byte[] data,String publicKey,int length) throws Exception
  37.     {
  38.         /*
  39.         * 将BASE64编码格式 publicKey进行解码
  40.         * */
  41.         byte[] publicKeyByte = decryptBASE64(publicKey);
  42.         /*
  43.         * 使用X509EncodedKeySpec类创建了一个X.509编码的KeySpec对象,并将publicKeyByte作为参数传入。
  44.         * 将公钥 [字符串] 解码成 [公钥对象] ,以便用于加密数据。
  45.          * */
  46.         X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyByte);
  47.         /*
  48.         * 通过KeyFactory获取了RSA的实例
  49.         * */
  50.         KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
  51.         /*
  52.         * 调用generatePublic方法使用之前创建的X509EncodedKeySpec对象来生成公钥。
  53.         * */
  54.         Key generatePublicKey = keyFactory.generatePublic(x509EncodedKeySpec);
  55.         /*
  56.         * 创建一个Cipher实例,它是用于加密或解密数据的对象。Cipher类提供了加密和解密功能,并支持许多不同的加密算法。
  57.         * 在这里,getInstance 方法中传入了keyFactory.getAlgorithm()[获取与指定密钥工厂相关联的算法名称。],它用于获取与指定算法关联的 Cipher 实例。
  58.         * */
  59.         Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
  60.         /*
  61.         * 初始化 Cipher 对象。
  62.         * 在初始化过程中,指定加密模式为 ENCRYPT_MODE,并传入了之前生成的公钥 generatePublicKey。
  63.         * */
  64.         cipher.init(Cipher.ENCRYPT_MODE,generatePublicKey);
  65.         int inputLen = data.length;
  66.         ByteArrayOutputStream out = new ByteArrayOutputStream();
  67.         //段落起始位置
  68.         int offSet = 0;
  69.         byte[] cache;
  70.         int i = 0;
  71.         //对数据进行分段加密
  72.         while (inputLen - offSet > 0)
  73.         {
  74.             if (inputLen - offSet > length) {
  75.                 cache = cipher.doFinal(data,offSet,length);
  76.             } else {
  77.                 cache = cipher.doFinal(data,offSet,inputLen-offSet);
  78.             }
  79.             out.write(cache,0,cache.length);
  80.             i++;
  81.             offSet = i * length;
  82.         }
  83.         byte[] encryptDate = out.toByteArray();
  84.         out.close();
  85.         return encryptDate;
  86.     }
  87.     /**
  88.      * Description:
  89.      *
  90.      * @param data 待解密数据
  91.      * @param privateKey 私密(BUSE64编码)
  92.      * @param length 分段解密长度 128
  93.      * @return byte[]
  94.     */
  95.     public static byte[] decryptByPrivateKey(byte[] data,String privateKey,int length) throws Exception
  96.     {
  97.         byte[] privateKeyByte = decryptBASE64(privateKey);
  98.         PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyByte);
  99.         KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
  100.         Key generatePrivateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
  101.         Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
  102.         cipher.init(Cipher.DECRYPT_MODE,generatePrivateKey);
  103.         int inputLen = data.length;
  104.         ByteArrayOutputStream out = new ByteArrayOutputStream();
  105.         int offSet = 0;
  106.         byte[] cache;
  107.         int i = 0;
  108.         //对数据进行分段解密
  109.         while (inputLen - offSet > 0)
  110.         {
  111.             if (inputLen - offSet > length)
  112.             {
  113.                 cache = cipher.doFinal(data,offSet,length);
  114.             } else {
  115.                 cache = cipher.doFinal(data,offSet,inputLen - offSet);
  116.             }
  117.             out.write(cache,0,cache.length);
  118.             i++;
  119.             offSet = i * length;
  120.         }
  121.         byte[] decryptData = out.toByteArray();
  122.         out.close();
  123.         return decryptData;
  124.     }
  125.     /**
  126.      * Description: BASE64解码
  127.      *
  128.      * @param src
  129.      * @return byte[]
  130.     */
  131.     public static byte[] decryptBASE64(String src)
  132.     {
  133.         sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
  134.         try{
  135.             return decoder.decodeBuffer(src);
  136.         }catch (Exception ex){
  137.             return null;
  138.         }
  139.     }
  140.     /**
  141.      * Description: BASE64编码
  142.      *
  143.      * @param src
  144.      * @return java.lang.String
  145.     */
  146.     public static String encryptBASE64(byte[] src)
  147.     {
  148.        sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
  149.         return encoder.encode(src);
  150.     }
  151.     /**
  152.      * Description: 从Byte[] 数组转 16进制字符串
  153.      *
  154.      * @param b
  155.      * @return java.lang.String
  156.     */
  157.     public static String toHexString(byte[] b)
  158.     {
  159.         /*
  160.         * 每个字节都可以用两个十六进制字符来表示,因此初始化的容量是字节数组长度的两倍。
  161.         * */
  162.         StringBuilder sb = new StringBuilder(b.length * 2);
  163.         for (int i = 0; i<b.length ;i++)
  164.         {
  165.             /*
  166.             * 首先取字节的高四位,然后查找对应的十六进制字符,并将其追加到StringBuilder中
  167.             * */
  168.             sb.append(HEXCHAR[(b[i] & 0xf0) >>> 4]);
  169.             /*
  170.             * 取字节的低四位,找到对应的十六进制字符,并追加到StringBuilder中。
  171.             * */
  172.             sb.append(HEXCHAR[b[i] & 0x0f]);
  173.         }
  174.         return sb.toString();
  175.     }
  176.     /**
  177.      * Description: 从16进制字符串转 byte[] 数组
  178.      *
  179.      * @param s
  180.      * @return byte[]
  181.     */
  182.     public static final byte[] toBytes(String s)
  183.     {
  184.         byte[] bytes;
  185.         bytes = new byte[s.length() / 2];
  186.         for (int i = 0; i < bytes.length ; i++)
  187.         {
  188.             bytes[i] = (byte) Integer.parseInt(s.substring(2*i,2*i+2),16);
  189.         }
  190.         return bytes;
  191.     }
  192.     /**
  193.      * Description: 判断对象是否为null
  194.      */
  195.     public static boolean isEmpty(Object str) {
  196.         return (str == null || "".equals(str));
  197.     }
  198.     /**
  199.      * RSA 公钥私钥生成器
  200.      * */
  201.     public static Map generateRandomToBase64Key() throws Exception{
  202.         String KEY_ALGORITHM ="RSA";
  203.         KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
  204.         //密钥位数
  205.         keyPairGenerator.initialize(1024);
  206.         //创建公钥/私钥
  207.         KeyPair keyPair = keyPairGenerator.generateKeyPair();
  208.         RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
  209.         RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
  210.         byte[] publicKeyEncoded = publicKey.getEncoded();
  211.         byte[] privateKeyEncoded = privateKey.getEncoded();
  212.         HashMap<String,String> map = new HashMap<>();
  213.         map.put("publicKey", Base64Encoder.encode(publicKeyEncoded));
  214.         map.put("privateKey",Base64Encoder.encode(privateKeyEncoded));
  215.         return map;
  216.     }
  217.     /**
  218.      * Description: 根据请求参数Map集合,排号顺序Sort,组装生成对应请求中的签名参数sign
  219.      *
  220.      * @param map 请求参数Map集合
  221.      * @param allowValueNull 是否允许map中的值为null; true允许:若允许为空则会出现a=&b=
  222.      * @return java.lang.String
  223.     */
  224.     public static  String  generateSortSign(Map<String,String> map,boolean allowValueNull)
  225.     {
  226.         List<String> keys = new ArrayList<>(map.size());
  227.         for ( String key : map.keySet() )
  228.         {
  229.             /*
  230.             * 排除下列参数数据
  231.             * 1.不允许出现空value 且 map中为null 的键值对
  232.             * 2.参数签名内容键值对
  233.             * */
  234.             if ( (!allowValueNull && isEmpty(map.get(key))) || "sign".equals(key) || "signValue".equals(key) )
  235.             {
  236.                 continue;
  237.             }
  238.             keys.add(key);
  239.         }
  240.         /*
  241.         * sort静态方法用于按自然顺序或自定义顺序对List进行排序
  242.         * */
  243.         Collections.sort(keys);
  244.         StringBuffer stringBuffer = new StringBuffer();
  245.         boolean isFirst = true;
  246.         for (String key : keys){
  247.             if (isFirst){
  248.                 stringBuffer.append(key).append("=").append(map.get(key));
  249.                 isFirst = false;
  250.                 continue;
  251.             }
  252.             stringBuffer.append("&").append(key).append("=").append(map.get(key));
  253.         }
  254.         return stringBuffer.toString();
  255.     }
  256.     /**
  257.      * Description: 用于生成数据的数字签名,并将签名数据转换为十六进制字符串格式返回
  258.      *
  259.      * @param rawDate 签名裸数据
  260.      * @param privateKey 私钥
  261.      * @param algorithm 签名验算算法
  262.      * @return java.lang.String
  263.     */
  264.     public static String generateSign(byte[] rawDate,String privateKey,String algorithm) throws Exception
  265.     {
  266.         byte[] privateKeyBytes = decryptBASE64(privateKey);
  267.         PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
  268.         KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
  269.         PrivateKey generatePrivate = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
  270.         /*
  271.         * 使用指定的签名算法algorithm,通过Signature实例获取签名对象signature。
  272.         * */
  273.         Signature signature = Signature.getInstance(algorithm);
  274.         /*
  275.         * 初始化签名对象,传入生成的私钥generatePrivate。
  276.         * */
  277.         signature.initSign(generatePrivate);
  278.         /*
  279.         * 将要签名的裸数据rawData传入签名对象。
  280.         * */
  281.         signature.update(rawDate);
  282.         /*
  283.         * 生成签名数据sign
  284.         * */
  285.         byte[] sign = signature.sign();
  286.         return toHexString(sign);
  287.     }
  288.     /**
  289.      * Description: 验证签名
  290.      *
  291.      * @param data 请求数据
  292.      * @param publicKey 公钥
  293.      * @param sign 签名数据
  294.      * @param algorithm 签名验算算法
  295.      * @return boolean
  296.     */
  297.     public static boolean verify(byte[] data,String publicKey,String sign,String algorithm) throws Exception
  298.     {
  299.         byte[] publicKeyBytes = decryptBASE64(publicKey);
  300.         X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
  301.         KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
  302.         PublicKey generatePublicKey = keyFactory.generatePublic(x509EncodedKeySpec);
  303.         Signature signature = Signature.getInstance(algorithm);
  304.         signature.initVerify(generatePublicKey);
  305.         signature.update(data);
  306.         return signature.verify( toBytes(sign) );
  307.     }
  308. }
复制代码
RSAUtil加解密工具类方法代码解析(为了方便自己理解和利用,有须要简单分类记录一下工具类中的方法接口):

byte[] decryptBASE64(String src)
String encryptBASE64(byte[] src)

String toHexString(byte[] b)
byte[] toBytes(String s)

byte[] encryptByPublicKey(byte[] data,String publicKey,int length)
RSAUtil工具类中的加密接口就只有 一个,终极加密后的数据会以字节数组 byte[] 格式返回。终极用户想将密文以什么形式传输都可以( String() 字符串形式、Base64编码格式、HexString十六进制字符串形式 )。
byte[] decryptByPrivateKey(byte[] data,String privateKey,int length)
在解码数据时,必须将密文数据根据对应的数据格式转换为 byte[] 后传入解码方法。

byte[] decryptBASE64(String src)
String encryptBASE64(byte[] src)



二、解密传输数据实现方案

依托与SpringBoot举行开发,在后台中必要解密的请求接口,是采用了FIlter来实现解密操作。

采用 FIlter 来对加密数据举行解密的好处之一是:Filter 获取到参数后,可以将密文参数解密之后,重新重写请求参数。如许在Controller层处理业务逻的接口可以按照正常方式举行开发,@RequestBody、@RequestParam 等注解都能正常利用。


2.1 Request 流只能读取一次的问题

在接口调用连接中,request的请求流只能调用一次,处理之后,如果之后还必要用到请求流获取数据,就会发现数据为空。好比利用了filter大概aop在接口处理之前,获取了request中的数据,对参数举行了校验,那么之后就不能在获取request请求流了。

继续HttpServletRequestWrapper,将请求中的流copy一份,复写getInputStream和getReader等方法供外部利用。每次调用后的getInputStream方法都是从复制出来的二进制数组中举行获取,这个二进制数组在对象存在期间一致存在。通过HttpServletRequestWrapper可以获取到前端加密的请求参数,同时也可以将解密后的参数设置进去。
Post请求:采用Filter来实现 加密传输数据的解密功能,在解密对应request请求流中的数据之后。将解密后的数据替换至自定义封装的requestWrapper对象中 body中。
Get请求:地址栏中添加了加密数据,在Filter举行解密之后,会将请求数据存入自定义封装的 requestWrapper 对象中的 Map聚集数据中。在重写父类的 getParament() 等方法。
这里必要特别留意;对于MultipartRequest请求如果不做处理HttpServletRequestWrapper中是获取不到参数的;

  1. package com.example.api_security_demo.common.core.wrapper;
  2. import com.alibaba.fastjson2.JSONObject;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.web.multipart.MultipartRequest;
  5. import javax.servlet.ReadListener;
  6. import javax.servlet.ServletInputStream;
  7. import javax.servlet.ServletRequest;
  8. import javax.servlet.http.HttpServletRequest;
  9. import javax.servlet.http.HttpServletRequestWrapper;
  10. import java.io.*;
  11. import java.nio.charset.Charset;
  12. import java.util.*;
  13. /**
  14. * @ClassName : RequestWrapper
  15. * @Description : 自定义Request,解决request请求流中的数据二次或多次使用问题
  16. *  继承HttpServletRequestWrapper,将请求体中的流copy一份,覆写getInputStream()和getReader()方法供外部使用。
  17. *  每次调用覆写后的getInputStream()方法都是从复制出来的二进制数组中进行获取,这个二进制数组在对象存在期间一直存在,这样就实现了流的重复读取。
  18. * @Author : AD
  19. */
  20. @Slf4j
  21. public class RequestWrapper extends HttpServletRequestWrapper {
  22.     /**
  23.      * 存储Body数据
  24.      * */
  25.     private byte[] body;
  26.     //============
  27.     /**
  28.      * 保存原始Request对象,当请求为 MultipartRequest 文件上传类的请求操作
  29.      */
  30.     private HttpServletRequest request;
  31.     /**
  32.      * 额外参数可以加到这个里面 重写getParameter() 方法 ,从而使请求中不存在的参数,通过该Map集合中获取!!
  33.      */
  34.     private Map<String, String[]> parameterMap = new LinkedHashMap<>();
  35.     /**
  36.      * Description: requestWrapper 请求包装类的构造方法
  37.      *
  38.      * @param request
  39.      * @return
  40.      */
  41.     public RequestWrapper(HttpServletRequest request)throws IOException
  42.     {
  43.         super(request);
  44.         //[文件上传相关的操作]
  45.         this.request = request;
  46.         if(request instanceof MultipartRequest){
  47.             // 如果是[文件上传类]请求
  48.             this.parseBody(request);
  49.         }else{
  50.             //[普通请求类]将Body数据存储起来
  51.             String bodyString = getBodyString(request);
  52.             body = bodyString.getBytes(Charset.defaultCharset());
  53.         }
  54.     }
  55.     /* [MultipartRequest 文件上传]相关操作接口 */
  56.     /**
  57.      * 如果是 MultipartRequest,需要解析参数信息
  58.      */
  59.     private void parseBody(HttpServletRequest request) {
  60.         Map<String,Object> parameterMap = new LinkedHashMap<>();
  61.         Enumeration<String> parameterNames = request.getParameterNames();
  62.         while(parameterNames.hasMoreElements()){
  63.             String name = parameterNames.nextElement();
  64.             String[] values = request.getParameterValues(name);
  65.             parameterMap.put(name, (values !=null && values.length == 1) ? values[0] : values);
  66.         }
  67.         // 将解析出来的参数,转换成JSON并设置到body中保存
  68.         this.body = JSONObject.toJSONString(parameterMap).getBytes(Charset.defaultCharset());
  69.     }
  70.     public void setBody(byte[] body)
  71.     {
  72.         this.body = body;
  73.         try {
  74.             if(this.request instanceof MultipartRequest){
  75.                 //[文件上传请求相关的操作]
  76.                 //todo 将Json格式body数据转换为mp
  77.                 //this.setParameterMap(JsonUtil.json2map(body));
  78.                 String bodyStr = new String(body, "UTF-8");
  79.                 com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
  80.                 Map<String, Object> bodyMap = objectMapper.readValue(bodyStr, Map.class);
  81.                 this.setParameterMap(bodyMap);
  82.             }
  83.         } catch (Exception e) {
  84.             log.error("转换参数异常,参数:{},异常:{}",body, e);
  85.         }
  86.     }
  87.     /**
  88.      * Description:读取请全体Body中数据 [从 requestWrapper中读取]
  89.      *
  90.      * @param
  91.      * @return java.lang.String
  92.      */
  93.     public String getBodyString(){
  94.         final InputStream inputStream  =  new ByteArrayInputStream(body);
  95.         return inputStreamToString(inputStream);
  96.     }
  97.     /**
  98.      * Description: 读取请求体Body中数据 [从HttpServletRequest中读取]
  99.      *
  100.      * @param request
  101.      * @return java.lang.String
  102.     */
  103.     public String getBodyString(final ServletRequest request)
  104.     {
  105.         try {
  106.             return inputStreamToString(request.getInputStream());
  107.         }catch (IOException e){
  108.             log.error("Read Request Body IO_Stream Fail !",e);
  109.             throw new RuntimeException(e);
  110.         }
  111.     }
  112.     /**
  113.      * Description: 将inputStream流里面的数据读取出来并转换为字符串形式
  114.      *
  115.      * @param inputStream
  116.      * @return java.lang.String
  117.     */
  118.     private String inputStreamToString(InputStream inputStream){
  119.         StringBuilder sb = new StringBuilder();
  120.         BufferedReader reader = null;
  121.         try {
  122.             reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
  123.             String line;
  124.             while ((line = reader.readLine()) != null){
  125.                 sb.append(line);
  126.             }
  127.         }catch (IOException e){
  128.             log.error("BufferedReader is Fail !",e);
  129.             throw new RuntimeException(e);
  130.         }finally {
  131.             if (reader != null){
  132.                 try {
  133.                     reader.close();
  134.                 } catch (IOException e) {
  135.                     throw new RuntimeException(e);
  136.                 }
  137.             }
  138.         }
  139.         return sb.toString();
  140.     }
  141.     @Override
  142.     public BufferedReader getReader() throws IOException {
  143.         return new BufferedReader(new InputStreamReader(getInputStream()));
  144.     }
  145.     @Override
  146.     public ServletInputStream getInputStream() throws IOException {
  147.         final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
  148.         return new ServletInputStream() {
  149.             @Override
  150.             public boolean isFinished() {
  151.                 return false;
  152.             }
  153.             @Override
  154.             public boolean isReady() {
  155.                 return false;
  156.             }
  157.             @Override
  158.             public void setReadListener(ReadListener readListener) {
  159.             }
  160.             @Override
  161.             public int read() throws IOException {
  162.                 return inputStream.read();
  163.             }
  164.         };
  165.     }
  166. // [Get请求相关操作,封装Request.getParameter()中的相关参数 ]
  167.     /**
  168.      * Description: The default behavior of this method is to return getParameter(Stringname) on the wrapped request object.
  169.      *
  170.      * @param name
  171.      * @return java.lang.String
  172.      * @date 2024-05-13
  173.     */
  174.     @Override
  175.     public String getParameter(String name) {
  176.         String result =  super.getParameter(name);
  177.         // 如果参数获取不到则尝试从参数(自定义封装的存贮零时请求数据的集合)Map中获取,并且只返回第一个
  178.         if(result==null && this.parameterMap.containsKey(name)){
  179.             result = this.parameterMap.get(name)[0];
  180.         }
  181.         return result;
  182.     }
  183.     /**
  184.      * The default behavior of this method is to return getParameterMap() on the
  185.      * wrapped request object.
  186.      */
  187.     @Override
  188.     public Map<String, String[]> getParameterMap() {
  189.         // 需要将原有的参数加上新参数 返回
  190.         Map<String,String[]> map = new HashMap<>(super.getParameterMap());
  191.         for(String key: this.parameterMap.keySet()){
  192.             map.put(key, this.parameterMap.get(key));
  193.         }
  194.         return Collections.unmodifiableMap(map);
  195.     }
  196.     /**
  197.      * The default behavior of this method is to return
  198.      * getParameterValues(String name) on the wrapped request object.
  199.      *
  200.      * @param name
  201.      */
  202.     @Override
  203.     public String[] getParameterValues(String name) {
  204.         String[] result =  super.getParameterValues(name);
  205.         if(result == null && this.parameterMap.containsKey(name)){
  206.             result = this.parameterMap.get(name);
  207.         }
  208.         return result;
  209.     }
  210.     /**
  211.      * The default behavior of this method is to return getParameterNames() on
  212.      * the wrapped request object.
  213.      */
  214.     @Override
  215.     public Enumeration<String> getParameterNames() {
  216.         Enumeration<String> parameterNames = super.getParameterNames();
  217.         Set<String> names = new LinkedHashSet<>();
  218.         if(parameterNames !=null){
  219.             while(parameterNames.hasMoreElements()){
  220.                 names.add(parameterNames.nextElement());
  221.             }
  222.         }
  223.         // 添加后期设置的参数Map
  224.         if(!this.parameterMap.isEmpty()){
  225.             names.addAll(this.parameterMap.keySet());
  226.         }
  227.         return Collections.enumeration(names);
  228.     }
  229.     /**
  230.      * 设置参数map
  231.      * @param json2map
  232.      */
  233.     public void setParameterMap(Map<String, Object> json2map) {
  234.         if(json2map != null && !json2map.isEmpty()) {
  235.             for (String key : json2map.keySet()){
  236.                 //获取map中对应key的value
  237.                 Object value = json2map.get(key);
  238.                 if(this.parameterMap.containsKey(key)){
  239.                     //如果额外参数HashLink中包含该参数,则在赋值加入到String[] 中
  240.                     String[] originalArray = this.parameterMap.get(key);
  241.                     int originalLength = originalArray.length;
  242.                     originalArray = Arrays.copyOf(originalArray,originalLength + 1);
  243.                     originalArray[originalLength] = String.valueOf(value);
  244.                     //this.parameterMap.put(key, Collection.add(this.parameterMap.get(key), value));
  245.                     this.parameterMap.put(key,originalArray);
  246.                 }else{
  247.                     this.parameterMap.put(key, new String[]{String.valueOf(value)});
  248.                 }
  249.             }
  250.         }
  251.     }
  252. }
复制代码
RequestWrapper类方法代码解析(为了方便自己理解和利用,有须要简单分类记录一下工具类中的方法接口):这里自定义封装的 RequestWrapper对象,继续 HttpServletRequestWrapper。

2.2 过滤器Filter解密请求参数

这里封装的解密参数过滤器Filter中,起首必要通过请求头中的 aksEncrypt数据判定该请求是否为加密请求,如果不是则直接放行不做解密操作。如果必要解密的请求,起首判定请求范例在举行对应的解密处理。

Filter解密数据过滤器代码:
  1. package com.example.api_security_demo.filter;
  2. import com.alibaba.fastjson2.JSONObject;
  3. import com.example.api_security_demo.common.core.wrapper.RequestWrapper;
  4. import com.example.api_security_demo.utils.AESUtil;
  5. import com.example.api_security_demo.utils.RSAUtil;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.beans.factory.annotation.Value;
  8. import org.springframework.util.ObjectUtils;
  9. import javax.servlet.*;
  10. import javax.servlet.http.HttpServletRequest;
  11. import java.io.BufferedReader;
  12. import java.io.IOException;
  13. import java.util.Arrays;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. /**
  17. * @ClassName : DecryptReplaceStreamFilter
  18. * @Description :Filter过滤器 解密请求参数。同时替换请求体,使后续操作无感知加密!!
  19. * @Author : AD
  20. */
  21. @Slf4j
  22. public class DecryptReplaceStreamFilter implements Filter {
  23.     /*
  24.     * AKS(Authentication Key Management System),采用无明文密钥的方式对数据进行加密保护,加解密运算由统一的安全计算中心完成,AKS系统以接口的方式为对各个业务线提供加密解服务。
  25.     * 各个系统不再使用明文密钥,只使用密钥别名,调用简单的加解密函数,完成对数据的保护。
  26.     * */
  27.     //请求头标签开关:是否需要加密解密
  28.     private static final String AKS_ENABLE = "aksEncrypt";
  29.     //GET请求加密数据Key
  30.     public static final String AKS_PARAMETER = "encryptData";
  31.     private static final String METHOD_GET = "GET";
  32.     private static final String METHOD_POST ="POST";
  33.     //POST请求加密数据Key
  34.     private static final String AKS_BODY ="content";
  35.     //AES密钥Key
  36.     private static final String AES_KEY = "aesKey";
  37.     /**
  38.      * Feign是声明式Web Service客户端,它让微服务之间的调用变得更简单,类似controller调用service。
  39.      * Feign内部调用时,请求头中标注请求源
  40.      * */
  41.     public static final String SOURCE_KEY = "api-source";
  42.     public static final String SOURCE_VALUE = "inner-api";
  43.     @Value("${Rsa.PrivateKey}")
  44.     private String privateKey;
  45.     @Value("${API.Security.enable}")
  46.     private boolean securityEnable;
  47.     @Override
  48.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  49.         //转换为自己Wrapper,实现多次读写
  50.         RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
  51.         String contentType = requestWrapper.getContentType();  //请求头中获取Content-Type数据
  52.         String requestURI = requestWrapper.getRequestURI();
  53.         //配置文件配置是否开启加解密功能
  54.         if (!securityEnable)
  55.         {
  56.             log.info("未开启接口安全加密传输! 无需解密请求参数!");
  57.             filterChain.doFilter(requestWrapper,servletResponse);
  58.             return;
  59.         }
  60.         //通过请求头参数判断该请求是否需要解密处理
  61.         if (!needAks(requestWrapper)){
  62.             log.info("请求:{},非加密请求,无需解密操作!",requestURI);
  63.             filterChain.doFilter(requestWrapper,servletResponse);
  64.             return;
  65.         }
  66.         /*
  67.         * [该功能暂时不用管,因为在请求头中不添加 aksEncrypt:true  键值对,请求接口时同样不会去进行解密请求数据的操作]
  68.         * */
  69.         //Feign服务端调用内部请求,按照不加密的逻辑放行[Feign是声明式Web Service客户端,它让微服务之间的调用变得更简单,类似controller调用service。]
  70.         //前端 --> A --> b --> c
  71.         String sourceKey = requestWrapper.getHeader(SOURCE_KEY);
  72.         if (!ObjectUtils.isEmpty(sourceKey) && sourceKey.equals(SOURCE_VALUE) ){
  73.             log.info("内部请求,无效加密解密接口数据!");
  74.             filterChain.doFilter(requestWrapper,servletResponse);
  75.             return;
  76.         }
  77.         /*
  78.         * POST请求进行解密工作
  79.         * */
  80.         if (requestWrapper.getMethod().equalsIgnoreCase(METHOD_POST)){
  81.             //读取JSON请求体数据
  82.             StringBuffer bodyInfo = new StringBuffer();
  83.             String line = null;
  84.             BufferedReader reader = null;
  85.             reader = requestWrapper.getReader();
  86.             while ( ( line = reader.readLine() ) != null ){
  87.                 bodyInfo.append(line);
  88.             }
  89.             //解密请求体数据
  90.             JSONObject jsonObject = JSONObject.parseObject(bodyInfo.toString());
  91.             log.info("Post请求:{},待解密请求参数:{}", requestURI, JSONObject.toJSONString(jsonObject));
  92.             //获取通过AES加密之后的密文
  93.             String content = jsonObject.getString(AKS_BODY);
  94.             //获取通过RSA加密之后的AES密钥KEy
  95.             String aesKey = jsonObject.getString(AES_KEY);
  96.             //RSAUtil解密出AES密钥Key
  97.             try {
  98.                 aesKey = new String(RSAUtil.decryptByPrivateKey(RSAUtil.toBytes(aesKey),privateKey,RSAUtil.MAX_DECRYPT_BLOCK),"UTF-8");
  99.             } catch (Exception e) {
  100.                 throw new RuntimeException(e);
  101.             }
  102.             /*
  103.             * 方式1.将解密之后的数据+aesKey [放入body中,弃:会影响body结构] ( 满足在AOP操作中对出参数据进行加密 ) 一并交给下游业务。
  104.             */
  105.             //JSONObject requestBody = JSONObject.parseObject(data);
  106.             //requestBody.put(AES_KEY,aesKey);
  107.             /*
  108.              * 方式2.将其放入[请求对象属性中中],不影响请求体结构!
  109.             */
  110.             //requestWrapper.setAttribute(AES_KEY,aesKey);
  111.             /*
  112.             * 方式3.将其放入[放入RequestWrapper封装的额外参数Map中],不影响请求体结构!
  113.             * */
  114.             HashMap<String, Object> map = new HashMap<>();
  115.             map.put(AES_KEY,aesKey);
  116.             requestWrapper.setParameterMap(map);
  117.             //AESUtil + aesKey 解密json数据
  118.             String decryptData = AESUtil.decryptFromBase64(content, aesKey);
  119.             log.info("Get请求:{},解密之后参数:{}", requestURI, decryptData);
  120.             //重置Json请求体,保证下游业务无感知获取数据
  121.             //requestWrapper.setBody(requestBody.toJSONString().getBytes());
  122.             requestWrapper.setBody(decryptData.getBytes());
  123.         }
  124.         /*
  125.          * GET请求解密处理:[AES密钥Key放置在请求头中]
  126.          * */
  127.         else if (requestWrapper.getMethod().equalsIgnoreCase(METHOD_GET))
  128.         {
  129.             //Get请求中 获取到指定加密的参数 然后进行解密操作
  130.             String encryptData = requestWrapper.getParameter(AKS_PARAMETER);
  131.             log.info("Get请求:{},待解密请求参数:{}", requestWrapper.getRequestURI(), encryptData);
  132.             // 先解密 存放在请求头中且经过RSA加密过的AES密钥
  133.             String aesKey = requestWrapper.getHeader(AES_KEY);
  134.             if (encryptData != null && !encryptData.isEmpty() && aesKey != null && !aesKey.isEmpty()){
  135.                 try {
  136.                     byte[] aesKeyByte = RSAUtil.decryptByPrivateKey(RSAUtil.toBytes(aesKey), privateKey, RSAUtil.MAX_DECRYPT_BLOCK);
  137.                     aesKey = new String(aesKeyByte,"UTF-8");
  138.                     System.out.println("aesKey.toString() = " + aesKey.toString());
  139.                 } catch (Exception e) {
  140.                     throw new RuntimeException(e);
  141.                 }
  142.                 //Get请求中的加密数据是以16进制字符串方式传输
  143.                 byte[] encryptDataByte = AESUtil.toBytes(encryptData);
  144.                 System.out.println("Arrays.toString(encryptDataByte) = " + Arrays.toString(encryptDataByte));
  145.                 // 解密数据操作 AKS_PARAMETER
  146.                 String decryptData = new String(AESUtil.decrypt(encryptDataByte,aesKey.getBytes("UTF-8")),"UTF-8");
  147.                 log.info("Get请求:{},解密之后参数:{}", requestURI, decryptData);
  148.                 //将GET请求中的Parameter参数赋值入RequestWrapper中封装的其它参数存放集合Map中
  149.                 com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
  150.                 Map<String, Object> map = objectMapper.readValue(decryptData, Map.class);
  151.                 //将aseKey数据也存入map额外参数中
  152.                 map.put("aesKey",aesKey);
  153.                 requestWrapper.setParameterMap(map);
  154.             }
  155.         }
  156.         filterChain.doFilter(requestWrapper,servletResponse);
  157.     }
  158.     @Override
  159.     public void init(FilterConfig filterConfig) throws ServletException {
  160.         Filter.super.init(filterConfig);
  161.     }
  162.     @Override
  163.     public void destroy() {
  164.         Filter.super.destroy();
  165.     }
  166.     /**
  167.      * Description: 判断当前请求是否需要加密解密数据
  168.      *
  169.      * @param request
  170.      * @return boolean
  171.     */
  172.     private boolean needAks(HttpServletRequest request){
  173.         String enableAKS = request.getHeader(AKS_ENABLE);
  174.         return (enableAKS != null && enableAKS.equalsIgnoreCase("true"))? true: false;
  175.     }
  176. }
复制代码
代码解析:

Filter过滤器注册配置类:
必要留意解密过滤器Filter 的优先级一点要设置为最高优先级registration.setOrder(1);,起首必要该过滤器对加密数据举行解密后在重新封装请求,才能使后续的业务数据能直接获取到解密后的数据,到达无感知的结果。
  1. package com.example.api_security_demo.filter;
  2. import org.springframework.boot.web.servlet.FilterRegistrationBean;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import javax.servlet.Filter;
  6. /**
  7. * @ClassName : WebAllHandlerConfig
  8. * @Description : 过滤器、监听器、拦截器 前置处理配置类
  9. * @Author : AD
  10. */
  11. @Configuration
  12. public class WebAllHandlerConfig {
  13.     /**
  14.      * 注册过滤器
  15.      * */
  16.     @Bean
  17.     public FilterRegistrationBean AllFilterRegistration(){
  18.         FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
  19.         registration.setFilter(decryptReplaceStreamFilter());
  20.         registration.addUrlPatterns("/*");
  21.         registration.setName("APISecurityFilter");
  22.         registration.setOrder(1);
  23.         return registration;
  24.     }
  25.     @Bean(name = "decryptReplaceStreamFilter")
  26.     public Filter decryptReplaceStreamFilter(){
  27.         return new DecryptReplaceStreamFilter();
  28.     }
  29. }
复制代码


三、响应数据加密实现方案

响应数据加密的实现方式是采用AOP来实现,可以对返回的结果对象举行处理,而Filter只能拿到Request与Response对象,处理不方便;
这里的响应数据加密方案就简单采用的是AES加密,通过前端传来的随机AES举行加密响应数据后在响应给前端!
这里响应加密的开启方式是通过自定义注解来实现的,创建自定义一个自定义注解,作为响应数据加密的切点,就实现了响应数据是否加密的开启。



3.1 自定义注解(开启加密的注解):

  1. package com.example.api_security_demo.common.core.annotation;
  2. import java.lang.annotation.*;
  3. /**
  4. * @ClassName : ResponseEncrypt
  5. * @Description : 响应数据加密开启注解
  6. * @Author : AD
  7. */
  8. @Target(ElementType.METHOD)
  9. @Retention(RetentionPolicy.RUNTIME)
  10. //@Documented
  11. public @interface ResponseEncrypt {
  12. }
复制代码

3.2 AOP实现响应数据加密功能:

必要留意,这里的响应加密AOP的优先级也要设置为最高@Order(value = 0),从而使最先开始的AOP能最后结束,如许才能包管 加密响应的AOP终极处理的响应数据是以是业务逻辑都处理结束后终极的响应结果,然后举行加密处理后响应给前端。
  1. package com.example.api_security_demo.aop;
  2. import com.example.api_security_demo.common.R;
  3. import com.example.api_security_demo.common.core.annotation.ResponseEncrypt;
  4. import com.example.api_security_demo.utils.AESUtil;
  5. import org.aspectj.lang.ProceedingJoinPoint;
  6. import org.aspectj.lang.annotation.Around;
  7. import org.aspectj.lang.annotation.Aspect;
  8. import org.aspectj.lang.annotation.Pointcut;
  9. import org.springframework.core.annotation.Order;
  10. import org.springframework.stereotype.Component;
  11. import org.springframework.web.context.request.RequestContextHolder;
  12. import org.springframework.web.context.request.ServletRequestAttributes;
  13. import javax.servlet.http.HttpServletRequest;
  14. import javax.servlet.http.HttpServletResponse;
  15. /**
  16. * @ClassName : ResponseEncryptAOP
  17. * @Description : 传输加密模块AOP,对接口的出参进行加密,注意顺序不能乱,
  18. * 此AOP必须第一个执行,因为最先执行的最后结束,这样才能在各个AOP都执行完毕之后完成最后的加密
  19. * @Author : AD
  20. */
  21. @Order(value = 0)
  22. @Aspect
  23. @Component
  24. public class ResponseEncryptAop {
  25.     @Pointcut("@annotation(com.example.api_security_demo.common.core.annotation.ResponseEncrypt)")
  26.     public void point(){}
  27.     /**
  28.      * 环绕增强,加密出参
  29.      * */
  30.     @Around(value = "point() && @annotation(responseEncrypt)")
  31.     public Object aroundEncrypt(ProceedingJoinPoint joinPoint, ResponseEncrypt responseEncrypt) throws Throwable {
  32.         //返回的结合
  33.         Object returnValue = null;
  34.         //从上下文中提取
  35.         HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  36.         HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
  37.         //业务执行返回结果
  38.         returnValue = joinPoint.proceed();
  39.         System.out.println("returnValue.toString() = " + returnValue.toString());
  40.         //获取到前端传递过来的AES密钥,然后对响应数据进行AES加密操作
  41.         String aesKey = request.getParameter("aesKey");
  42.         System.out.println("aesKey = " + aesKey);
  43.         String encryptToBase64Data = AESUtil.encryptToBase64(returnValue.toString(), aesKey);
  44.         returnValue = R.ok(encryptToBase64Data);
  45.         return returnValue;
  46.     }
  47. }
复制代码



四、测试相干的类


4.1 测试类实体封装

  1. package com.example.api_security_demo.controller;
  2. import lombok.Data;
  3. import lombok.ToString;
  4. import lombok.experimental.Accessors;
  5. /**
  6. * @ClassName : TbStudenEntity
  7. * @Description : 测试实体类
  8. * @Author : AD
  9. */
  10. @Data
  11. @Accessors(chain = true)
  12. @ToString
  13. public class TbStudentEntity {
  14.     int id;
  15.     String name;
  16.     String sex;
  17.     int age;
  18. }
复制代码

4.2 测试接口Controller层封装

  1. package com.example.api_security_demo.controller;
  2. import com.example.api_security_demo.common.R;
  3. import com.example.api_security_demo.common.core.annotation.ResponseEncrypt;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.web.bind.annotation.*;
  6. import javax.servlet.http.HttpServletRequest;
  7. /**
  8. * @ClassName : TbStudentController
  9. * @Description : 前端控制器Controller测试类
  10. * @Author : AD
  11. */
  12. @RestController
  13. @RequestMapping("/demo")
  14. @Slf4j
  15. public class TbStudentController {
  16.     @PostMapping("/encryptPost")
  17.     @ResponseEncrypt
  18.     public R postDemo(@RequestBody TbStudentEntity tbStudentEntity,HttpServletRequest request){
  19.         System.out.println("request.getParameter("aesKey") = " + request.getParameter("aesKey"));
  20.         //System.out.println("request.getAttribute("aesKey") = " + request.getAttribute("aesKey"));
  21.         System.out.println("tbStudentEntity = " + tbStudentEntity);
  22.         return R.ok(tbStudentEntity.toString());
  23.     }
  24.     @GetMapping("/encryptGet")
  25.     @ResponseEncrypt
  26.     public R postDemoGet(HttpServletRequest request,@RequestParam String id,@RequestParam String name,@RequestParam String aesKey ){
  27.         System.out.println("id = " + id);
  28.         System.out.println("name = " + name);
  29.         System.out.println("aesKey = " + aesKey);
  30.         System.out.println("request.getParameter("age") = " + request.getParameter("age"));
  31.         //System.out.println("request.getAttribute(DecryptReplaceStreamFilter.AKS_PARAMETER) = " + request.getAttribute(DecryptReplaceStreamFilter.AKS_PARAMETER));
  32.         return R.ok();
  33.     }
  34. }
复制代码

4.3 前端模拟生成加密数据

  1. package com.example.api_security_demo.utils;
  2. import com.alibaba.fastjson2.JSONObject;
  3. import com.fasterxml.jackson.core.JsonProcessingException;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.junit.Test;
  6. import java.io.UnsupportedEncodingException;
  7. import java.util.Arrays;
  8. import java.util.HashMap;
  9. import java.util.Map;
  10. /**
  11. * @ClassName : UtilsTestClass
  12. * @Description : 工具类测试类
  13. * @Author : AD
  14. */
  15. @Slf4j
  16. public class UtilsTestClass {
  17.     /**
  18.      * 可以将其放入配置文件中进行读取
  19.      * */
  20.     String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI7HE0aW9ILCMkfoAJYmAG+5RBRhU2Itebf04GUtnYMuR6Rl1GJKec7JKuM/8xSindH4jn6Vz3oARTjbCn4CxjbtQPys5i8VeXxgzzqhE34LY0Rt62Qy8UVS113454DTwZZR9BjmPQSxMaftQHMgeDjXVwLUt0a6CmRiZKOjw8WQIDAQAB";
  21.     String privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMjscTRpb0gsIyR+gAliYAb7lEFGFTYi15t/TgZS2dgy5HpGXUYkp5zskq4z/zFKKd0fiOfpXPegBFONsKfgLGNu1A/KzmLxV5fGDPOqETfgtjRG3rZDLxRVLXXfjngNPBllH0GOY9BLExp+1AcyB4ONdXAtS3RroKZGJko6PDxZAgMBAAECgYBRUHdkKcNypwI188gnhBuu18QhQpa1CRbPBI90ObWWLMqQvcdj6tO2y3t1au+9Z/FXXzrN+IC6apU1p2M2HaB4iPdW+2Vm0DaRN7XJPzBdJqVASYVJ2oWWLWCkG05mS2pAgcMlxV3TLen7iBFKTjgZhdLIal8JYgyi6XWdNlAtAQJBAPjCtpmXuAS9luyXo2ExEauKEC2uC06FeZqgM6u1rTqsqUE5kGpmJ1DiERuOcQxn8i+mlnJ1Urmw8ltMxmnJAiECQQDOxVbjUXWolwRF8lsOeZ9XGGc/45pE7J8DubEV0Ii1RVckMyTkXmgNAupE7Xq9cgNMpNBRUQEDNzSHhA8rqGM5AkBuwWrBac6RtcPLpRwl+s3uPTNE01fPZxgkYy1+Rw5QsG1PUAzfgooAthZ92Wa16lXnJ1mWrmvdp03Qnpc8pDVhAkBoqWD6vV/2D0L9eNh4cj2iY1rX7whGfRNcWmD1rtGUF94tF6pD4jl+5Ivaie6H+C8NW5uKnZsKmqX/NmxLZ/eZAkBDXRX7YgMG4PLXcjPT4ot6DEJDjuSVb1LhwucL6QFFYhZloH3/9XQS3T9XB/2F61tdh5Jd3FaCD7WeXJCzW+64";
  22.     /**
  23.      * Description: 测试获取密钥的方法
  24.     */
  25.     @Test
  26.     public void testGetASK() throws Exception {
  27.         // AES密钥
  28.         String aesKey = AESUtil.getAESKey(16);
  29.         System.out.println("aesKey = " + aesKey);
  30.         // AES Base64类型密钥
  31.         String base64AesKey = AESUtil.generateRandomAesKeyWithBase64();
  32.         System.out.println("base64AesKey = " + base64AesKey);
  33.         //RSA 私钥公钥
  34.         Map map = RSAUtil.generateRandomToBase64Key();
  35.         System.out.println("map.get("privateKey") = " + map.get("privateKey"));
  36.         System.out.println("map.get("publicKey") = " + map.get("publicKey"));
  37.     }
  38.     /**
  39.      * Description: post请求加密传输,请求体body封装测试
  40.      *
  41.      * @param
  42.      * @return void
  43.     */
  44.     @Test
  45.     public void postRequestBodyEncryptTest() throws Exception {
  46.         //自定义方式获取 aesKey
  47.         String aesKey = AESUtil.getAESKey(16);
  48.         //内部方式获取 Base64AesKey
  49.         String base64AesKey = AESUtil.generateRandomAesKeyWithBase64();
  50.         //创建请求体body数据 content
  51.         //模拟业务数据Json,并用AES密钥Key进行加密
  52.         Map<String, Object> dataMap = new HashMap<>();
  53.         dataMap.put("id", 15);
  54.         dataMap.put("name", "张三");
  55.         dataMap.put("sex", "男");
  56.         dataMap.put("age", 20);
  57.         JSONObject dataJson = new JSONObject(dataMap);
  58.         String contentJson = JSONObject.toJSONString(dataJson);
  59.         //AES 加密业务数据
  60.         String content = AESUtil.encryptToBase64(contentJson, aesKey);
  61.         log.info("AES加密数据操作: \nAES加密之前的数据 = {} \nAES加密之后的数据 content= {}",contentJson,content);
  62.         //RSA 加密AES密钥
  63.         byte[] bytes = RSAUtil.encryptByPublicKey(aesKey.getBytes("UTF-8"), publicKey, RSAUtil.MAX_ENCRYPT_BLOCK);
  64.         String encryptAesKey = RSAUtil.toHexString(bytes);
  65.         log.info("RSA加密AES密钥操作: \nRSA加密之前的AES密钥= {} \nRSA加密之后的AES密钥 aesKey={}",aesKey,encryptAesKey);
  66.         /**
  67.          * 09:08:42.376 [main] INFO com.example.api_security_demo.utils.UtilsTestClass - AES加密数据操作:
  68.          * AES加密之前的数据 = {"sex":"男","name":"张三","id":15,"age":20}
  69.          * AES加密之后的数据 content= u/vRpwktLAo12ATyfa1rb14EHNftHfvhYEfy7r+DOJzO6jzXS4bwUJ0xNY8RJu8f
  70.          * 09:08:42.380 [main] INFO com.example.api_security_demo.utils.UtilsTestClass - RSA加密AES密钥操作:
  71.          * RSA加密之前的AES密钥= 9raG41FIE8uK7l3k
  72.          * RSA加密之后的AES密钥 aesKey=c7eccef1d112075ee64eec65163b8b1dcb1a54ea6c8b51875174f6d34fc4ac7d50d2977b7519d275ee610d717d594228e132b053a70cdad9f925701a728ed794684d12097cfb8bea598c561393cb69de384b2ec83aa8ddb9a98a5adb3ed51ee1b9aaab2cf7bc5b49712a95e40ac4ea17421f5250d34b8e629e58db0b26e54b39
  73.          * */
  74.     }
  75.     /**
  76.      * Description: Get请求加密传输,请求体RequestParameter数据封装测试
  77.      * 注意: 通过AES加密之后的数据 byte[] 需要转换为 HexString十六进制字符串后才能在 请求地址中传输,不能在用Base64编码格式的方式进行传输!
  78.     */
  79.     @Test
  80.     public void getRequestBodyEncryptTest() throws Exception {
  81.         //自定义方式获取 aesKey
  82.         String aesKey = AESUtil.getAESKey(16);
  83.         //内部方式获取 Base64AesKey
  84.         String base64AesKey = AESUtil.generateRandomAesKeyWithBase64();
  85.         //创建请求体body数据 content
  86.         //模拟业务数据Json,并用AES密钥Key进行加密
  87.         Map<String, Object> dataMap = new HashMap<>();
  88.         dataMap.put("id", 15);
  89.         dataMap.put("name", "张三");
  90.         dataMap.put("sex", "男");
  91.         dataMap.put("age", 20);
  92.         JSONObject dataJson = new JSONObject(dataMap);
  93.         String contentJson = JSONObject.toJSONString(dataJson);
  94.         //AES 加密业务数据
  95.         byte[] contentByte = AESUtil.encrypt(contentJson.getBytes("UTF-8"), aesKey.getBytes("UTF-8"));
  96.         String content = AESUtil.toHexString(contentByte);
  97.         log.info("AES加密数据操作: \nAES加密之前的数据 = {} \nAES加密之后的Byte[]数据 = {}  \nAES加密后的数据转换为HexString十六进制字符串数据encryptData={}",contentJson,Arrays.toString(contentByte),content);
  98.         //RSA 加密AES密钥
  99.         byte[] bytes = RSAUtil.encryptByPublicKey(aesKey.getBytes("UTF-8"), publicKey, RSAUtil.MAX_ENCRYPT_BLOCK);
  100.         String encryptAesKey = RSAUtil.toHexString(bytes);
  101.         log.info("RSA加密AES密钥操作: \nRSA加密之前的AES密钥= {} \nRSA加密之后的AES密钥aesKey={}",aesKey,encryptAesKey);
  102.         /**
  103.          * 09:09:13.172 [main] INFO com.example.api_security_demo.utils.UtilsTestClass - AES加密数据操作:
  104.          * AES加密之前的数据 = {"sex":"男","name":"张三","id":15,"age":20}
  105.          * AES加密之后的Byte[]数据 = [73, 19, 107, 60, 29, 109, 100, 119, -81, -117, -84, -85, 19, 28, 86, 18, 123, 48, 58, 37, -28, -65, -93, -124, -50, 89, -10, -101, 48, 48, -104, -18, -109, 127, -19, 80, 62, -122, -80, -122, 94, 72, -16, -89, -1, -128, 22, -92]
  106.          * AES加密后的数据转换为HexString十六进制字符串数据encryptData=49136b3c1d6d6477af8bacab131c56127b303a25e4bfa384ce59f69b303098ee937fed503e86b0865e48f0a7ff8016a4
  107.          * 09:09:13.177 [main] INFO com.example.api_security_demo.utils.UtilsTestClass - RSA加密AES密钥操作:
  108.          * RSA加密之前的AES密钥= 868E9FMA727S9W5q
  109.          * RSA加密之后的AES密钥aesKey=2ab5531c7814201b4eaef3802ca883e79ffffd4c4ec32e698403189c0954718fd5cebd0ac5e66e856ec4f95a408442fc76276586a8fdb94c14c8f311f30ad061d6928315078736e6633113cdba255870a78e9077b2f18bdc4b2730804e5d6181df4b0ecf51597f71c8e0ccb89a5e160f1216a7bde5386b42171577db400d5a54
  110.          * */
  111.     }
  112.     @Test
  113.     public void testByteBase64String(){
  114.         /*
  115.          * 创建字节数组的方式
  116.          * */
  117.         byte[] testBytes = new byte[]{0,1,2,3,4};
  118.         /*
  119.          * 读取字节数组的方式:
  120.          * */
  121.         System.out.println("Arrays.toString(bytes1) = " + Arrays.toString(testBytes));
  122.         //模拟业务数据Json,并用AES密钥Key进行加密
  123.         Map<String, String> dataMap = new HashMap<>(2);
  124.         dataMap.put("name", "张三");
  125.         dataMap.put("age", "20");
  126.         JSONObject dataJson = new JSONObject(dataMap);
  127.         String jsonStr = JSONObject.toJSONString(dataJson);
  128.         com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
  129.         try {
  130.             Map<String, Object> map = objectMapper.readValue(jsonStr, Map.class);
  131.             System.out.println("map.toString() = " + map.toString());
  132.             System.out.println("map.get("name") = " + map.get("name"));
  133.             System.out.println("map.get("age") = " + map.get("age"));
  134.         } catch (JsonProcessingException e) {
  135.             throw new RuntimeException(e);
  136.         }
  137.         byte[] jsonByte = dataJson.toString().getBytes();
  138.         System.out.println("new String(jsonByte) = " + new String(jsonByte));
  139.         //[两种方式不一样!]
  140.         String hexStr = "0123456789abcdef";
  141.         System.out.println("Arrays.toString(hexStr.getBytes()) = " + Arrays.toString(hexStr.getBytes()));
  142.         System.out.println("Arrays.toString(RSAUtil.toBytes(hexStr)) = " + Arrays.toString(RSAUtil.toBytes(hexStr)));
  143.     }
  144.     /**
  145.      * Description: 解密操作测试
  146.     */
  147.     @Test
  148.     public void testAesDecrypt() throws Exception {
  149.         String encryptAesKey = "868dbf00b6849a1189f186f18bc98eca65981829e12d3bad21f4a64c139a6fe6953729e488af642cb5bf7104459a4fbf084bb536f251e2d9fa39747037715da6a73caf23e1d68bd5338d51dd207ebe9c4a72749d87d73eb96fc193adac45e6b8b6b7fcc211ee47efd0d54ea97dcfdbc221ac0bd7664d32becbdd654c3d9b2446";
  150.         String encryptDate = "8ee240b806a571f0f7ef5568d9cf5e36d999686acabfa4d5425d73ef7e546c8c1e9147c084269a6884cfeebf6759bd60";
  151.         //解密之后的AesKey 56X8817GRC2p33w0
  152.         byte[] decryptAesKey = RSAUtil.decryptByPrivateKey(RSAUtil.toBytes(encryptAesKey), privateKey, RSAUtil.MAX_DECRYPT_BLOCK);
  153.         String decryptAesKeyStr = new String(decryptAesKey, "UTF-8");
  154.         System.out.println("decryptAesKeyStr = " + decryptAesKeyStr);
  155.         //GET请求数据解密 [加密数据为HexStr十六进制字符串形式]
  156.         byte[] encryptDataByte = AESUtil.toBytes(encryptDate);
  157.         System.out.println("Arrays.toString(encryptDataByte) 加密数据Byte[]形式 = " + Arrays.toString(encryptDataByte));
  158.         String decryptData = new String(AESUtil.decrypt(encryptDataByte,decryptAesKeyStr.getBytes("UTF-8")),"UTF-8");
  159.         System.out.println("decryptData解密后的数据 = " + decryptData);
  160.       /*  //POST请求数据解密 [加密数据为Base64编码格式]
  161.         String decryptData2 = AESUtil.decryptFromBase64(encryptDate, decryptAesKeyStr);
  162.         System.out.println("decryptData2 解密后的数据 = " + decryptData2);*/
  163.         /**
  164.          * AES加密之前的数据 = {"sex":"男","name":"张三","id":15,"age":20}
  165.          * AES加密之后的Byte[]数据 = [-114, -30, 64, -72, 6, -91, 113, -16, -9, -17, 85, 104, -39, -49, 94, 54, -39, -103, 104, 106, -54, -65, -92, -43, 66, 93, 115, -17, 126, 84, 108, -116, 30, -111, 71, -64, -124, 38, -102, 104, -124, -49, -18, -65, 103, 89, -67, 96]
  166.          * AES加密后的数据转换为HexString十六进制字符串数据encryptData=8ee240b806a571f0f7ef5568d9cf5e36d999686acabfa4d5425d73ef7e546c8c1e9147c084269a6884cfeebf6759bd60
  167.          * 16:48:25.095 [main] INFO com.example.api_security_demo.utils.UtilsTestClass - RSA加密AES密钥操作:
  168.          * RSA加密之前的AES密钥= 8c0N9032214LJ139
  169.          * RSA加密之后的AES密钥aesKey=868dbf00b6849a1189f186f18bc98eca65981829e12d3bad21f4a64c139a6fe6953729e488af642cb5bf7104459a4fbf084bb536f251e2d9fa39747037715da6a73caf23e1d68bd5338d51dd207ebe9c4a72749d87d73eb96fc193adac45e6b8b6b7fcc211ee47efd0d54ea97dcfdbc221ac0bd7664d32becbdd654c3d9b2446
  170.          * */
  171.     }
  172.     /**
  173.      * Description: 响应数据加密后,模拟前端进行解密操作
  174.     */
  175.     @Test
  176.     public void responseEncryptToDecrypt() throws UnsupportedEncodingException {
  177.         String aesKeyPost = "9raG41FIE8uK7l3k";
  178.         String aesKeyGet = "0303F71572405EF1";
  179.         String encryptData = "cf156ddc9cd9fde2a8287fa3b8eadca258ce063130d29d7d9c1b949a4628c42e497b4e1db76244bdd075cb37b8ef0212";
  180.         //解密后的数据
  181.         String decryptData = "";
  182.         //Base64编码格式的数据解密
  183.         //decryptData = AESUtil.decryptFromBase64(encryptData, aesKeyGet);
  184.         //HexString格式的数据转换后在解密
  185.         byte[] decrypt = AESUtil.decrypt(AESUtil.toBytes(encryptData), aesKeyGet.getBytes("UTF-8"));
  186.         decryptData = new String(decrypt, "UTF-8");
  187.         System.out.println("decryptData = " + decryptData);
  188.     }
  189. }
复制代码

4.4 模拟加密请求调用接口

4.4.1 Post请求的封装


aksEncrypt: true 开启解密功能


content:通过随机AES密钥加密后的请求数据
aesKey: 通过RSA加密后的随机AES密钥


可以看出 请求数据解密后,通过 RequestWrapper重新封装后的请求数据,能直接通过@RequestBody 等注解直接获取到相请求体中的数据。 同时也可以通过 request.getParameter()中存放的参数。

4.4.2 Get请求的封装





可以看出 请求数据解密后,通过 RequestWrapper重新封装后的请求数据,能直接通过@RequestParam等注解直接获取到Get请求中的数据。 同时也可以通过 request.getParameter()中存放的参数。

[结束]:还有些地方封装的不是很适当,欢迎各位大佬留言提意见!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4