当前位置: 首页 > news >正文

朋友圈网站文章怎么做的wordpress 虎嗅模板

朋友圈网站文章怎么做的,wordpress 虎嗅模板,网站上的弹框如何做网页,谷歌paypal下载在Web应用中#xff0c;确保前后端之间的数据传输安全是非常重要的。这通常涉及到使用HTTPS协议、数据加密、令牌验证等安全措施。本文通过将前后端之间的传输数据进行加密#xff0c;用于在Spring Boot应用中实现前后端传输加密设计。 一、数据加密方案 即使使用了HTTPS确保前后端之间的数据传输安全是非常重要的。这通常涉及到使用HTTPS协议、数据加密、令牌验证等安全措施。本文通过将前后端之间的传输数据进行加密用于在Spring Boot应用中实现前后端传输加密设计。 一、数据加密方案 即使使用了HTTPS也可能需要在应用层对数据进行额外的加密。这可以通过以下方式实现 对称加密加密解密是同一个密钥速度快数据接收方需要公布其私钥给数据传输方进行数据加密安全性完全依赖于该密钥。适合做大量数据或数据文件的加解密。 使用AES、DES等对称加密算法对敏感数据进行加密和解密。前后端需要共享一个密钥key用于加密和解密。密钥的管理和传输需要特别注意安全性。 非对称加密加密用公钥解密用私钥。公钥和私钥是成对的(可借助工具生成如openssl等)即用公钥加密的数据一定能用其对应的私钥解密能用私钥解密的数据一定是其对应的公钥加密。对大量数据或数据文件加解密时效率较低。数据接收方需公布其公钥给数据传输方私钥自己保留安全性更高。 使用RSA、ECC等非对称加密算法。私钥用于加密数据公钥用于解密数据。公钥可以公开而私钥需要安全存储。 混合加密 结合使用对称加密和非对称加密。使用非对称加密算法交换对称加密的密钥会话密钥然后使用会话密钥进行实际的数据加密和解密。 这里就赘述介绍每种加密的实现方式和原理。 1.1 数据加密实现方式 如果数据传输较大密钥不需要进行网络传输数据不需要很高的安全级别则采用对称加密只要能保证密钥没有人为外泄即可 如果数据传输小而且对安全级别要求高或者密钥需要通过internet交换则采用非对称加密 本文采用了两者结合的方式(混合加密模式)这样是大多数场景下采用的加密方式。加密时序图如下所示 通过使用对称加密(AES) 和 非对称加密(RSA) 的方式来实现对数据的加密即通过对称加密进行业务数据体的加密通过非对称加密进行对称加密密钥的加密它结合了对称加密的高效性 和 非对称加密的安全性。 注意事项 确保RSA公钥在传输过程中是安全的因为任何拥有这个公钥的人都可以用它来加密AES密钥但只有拥有私钥的人才能解密它。确保在加密和解密过程中使用安全的加密库和最新的加密算法标准。定期更换密钥对和对称密钥以降低密钥泄露的风险。 这种混合加密模式提供了安全性和效率之间的平衡。对称加密如AES用于加密大量数据因为它通常比非对称加密更快。而非对称加密如RSA用于加密密钥因为它提供了更强的安全性特别是当密钥需要在不安全的通道上传输时。 1.2 AES加密工具类创建 封装AESUtil工具类时 pom.xml 中运用到的 依赖 !-- hutool-all工具类依赖 -- dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.8.18/version /dependencyAES加解密工具类 AESUtil 代码 package com.example.api_security_demo.utils;import cn.hutool.core.codec.Base64;import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Random;/*** ClassName : AESUtil* Description : AES加密工具类* Author : AD*/ public class AESUtil {public static final String CHAR_ENCODING UTF-8;/*** [常见算法]AES、DES、RSA、Blowfish、RC4 等等* [常见的模式] ECB (电子密码本模式)、CBC (密码分组链接模式)、CTR (计数模式) 等等* [常见的填充] NoPadding、PKCS5Padding、PKCS7Padding 等等** [AES算法]可以有以下几种常见的值* AES标准的AES算法。* AES/CBC/PKCS5Padding使用CBC模式和PKCS5填充的AES算法。* AES/ECB/PKCS5Padding使用ECB模式和PKCS5填充的AES算法。* AES/GCM/NoPadding使用GCM模式的AES算法不需要填充。* AES/CCM/NoPadding使用CCM模式的AES算法不需要填充。* AES/CFB/NoPadding使用CFB模式的AES算法不需要填充。* */public static final String AES_ALGORITHM AES;public static char[] HEXCHAR {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f};/*** Description: 随机生成 AESKey密钥** param length 随机生成密钥长度* return java.lang.String*/public static String getAESKey(int length) throws Exception{/** Random类用于生成伪随机数。* */Random random new Random();StringBuilder ret new StringBuilder();for(int i 0; i length; i){// 选择生成数字还是字符boolean isChar (random.nextInt(2) % 2 0);/* 0随机生成一个字符*/if (isChar){// 选择生成大写字母 / 小写字母int choice (random.nextInt(2) % 2 0) ? 65 : 97;ret.append((char) (choicerandom.nextInt(26)));/* 1随机生成一个数字 */}else{ret.append( random.nextInt(10));}}return ret.toString();}/*** Description: 加密** param data 待加密数据内容* param aesKey 加密密钥* return byte[]*/public static byte[] encrypt(byte[] data,byte[] aesKey){if (aesKey.length ! 16){throw new RuntimeException(Invalid AES key length (must be 16 bytes) !);}try{/** 创建一个SecretKeySpec对象来包装AES密钥。* 它使用了aesKey字节数组作为密钥并指定算法为AES。* 这个对象用来提供对称加密算法的密钥。* */SecretKeySpec secretKey new SecretKeySpec(aesKey, AES);/** 获取SecretKeySpec对象中的编码形式将其存储在encodedFormat字节数组中。* 这个编码形式可以被用来重新构造密钥。* */byte[] encodedFormat secretKey.getEncoded();/** 使用encodedFormat字节数组创建了另一个SecretKeySpec对象secKey。* 这个对象也用来提供对称加密算法的密钥。* */SecretKeySpec secKey new SecretKeySpec(encodedFormat, AES);/** 使用Cipher类的getInstance()方法获取了一个Cipher对象(创建密码器)。* 这个对象用来完成加密或解密的工作。* */Cipher cipher Cipher.getInstance(AES_ALGORITHM);/** 码调用init()方法来初始化Cipher对象(初始化)。* 它要求传入操作模式和提供密钥的对象这里使用Cipher.ENCRYPT_MODE代表加密模式以及之前创建的secKey对象作为密钥。* */cipher.init(Cipher.ENCRYPT_MODE,secKey);/** 用Cipher对象对data进行加密操作得到加密后的结果存储在result字节数组中。* */byte[] result cipher.doFinal(data);return result;}catch (Exception e){throw new RuntimeException( encrypt fail! ,e);}}/*** Description: 解密** param data 解密数据* param aesKey 解密密钥* return byte[]*/public static byte[] decrypt(byte[] data,byte[] aesKey){if (aesKey.length ! 16){throw new RuntimeException( Invalid AES Key length ( must be 16 bytes));}try {SecretKeySpec secretKeySpec new SecretKeySpec(aesKey, AES);byte[] encodedFormat secretKeySpec.getEncoded();SecretKeySpec secKey new SecretKeySpec(encodedFormat, AES);/* 创建密码器 */Cipher cipher Cipher.getInstance(AES_ALGORITHM);/* 初始化密码器 */cipher.init(Cipher.DECRYPT_MODE,secKey);byte[] result cipher.doFinal(data);return result;}catch (Exception e){throw new RuntimeException( Decrypt Fail !,e);}}/*** Description:加密数据并转换为Base64编码格式** param data 待加密数据* param aeskey 加密密钥* return java.lang.String*/public static String encryptToBase64(String data,String aeskey){try {byte[] valueByte encrypt(data.getBytes(CHAR_ENCODING), aeskey.getBytes(CHAR_ENCODING));/* 加密数据转 Byte[]-- 换为Base64 -- String */return Base64.encode(valueByte);}catch (UnsupportedEncodingException e){throw new RuntimeException( Encrypt Fail !,e);}}/*** Description: 解密数据将Basse64格式的加密数据进行解密操作** param data* param aeskey* return java.lang.String*/public static String decryptFromBase64(String data,String aeskey){try {byte[] originalData Base64.decode(data.getBytes());byte[] valueByte decrypt(originalData,aeskey.getBytes(CHAR_ENCODING));return new String(valueByte,CHAR_ENCODING);}catch (UnsupportedEncodingException e){throw new RuntimeException(Decrypt Fail !,e);}}/*** Description:加密数据aesKey为Base64格式时并将加密后的数据转换为Base64编码格式** param data* param aesKey* return java.lang.String*/public static String encryptWithKeyBase64(String data,String aesKey){try{byte[] valueByte encrypt(data.getBytes(CHAR_ENCODING), Base64.decode(aesKey.getBytes()));return Base64.encode(valueByte);}catch (UnsupportedEncodingException e){throw new RuntimeException(Encrypt Fail!,e);}}/*** Description: 解密数据数据源为Base64格式且 aesKey为Base64编码格式** param data* param aesKey* return java.lang.String*/public static String decryptWithKeyBase64(String data,String aesKey){try {byte[] originalDate Base64.decode(data.getBytes());byte[] valueByte decrypt(originalDate,Base64.decode(aesKey.getBytes()));return new String(valueByte,CHAR_ENCODING);}catch (UnsupportedEncodingException e){throw new RuntimeException(Decrypt Fail !,e);}}/*** Description:通过密钥生成器生成一个随机的 AES 密钥并将其以字节数组的形式返回。* 主要功能是生成并返回一组随机的密钥字节数组这些字节数组可用于加密和解密数据。** param* return byte[]*/public static byte[] generateRandomAesKey(){KeyGenerator keyGenerator null;try{/** KeyGenerator是Java Cryptography ArchitectureJCA提供的主要密钥生成器类之一用于生成对称加密算法的密钥。* 获取一个用于生成AES算法密钥的KeyGenerator实例以便在加密和解密操作中使用该密钥。* */keyGenerator KeyGenerator.getInstance(AES_ALGORITHM);}catch (NoSuchAlgorithmException e){throw new RuntimeException(GenerateRandomKey Fail ,e);}/** SecureRandom 类提供了一种用于生成加密强随机数的实现。* */SecureRandom secureRandom new SecureRandom();/** 初始化密钥生成器 keyGenerator。* 初始化密钥生成器时使用了 SecureRandom 实例以确保生成的密钥具有足够的随机性。* */keyGenerator.init(secureRandom);/** 调用 generateKey() 方法使用初始化后的 keyGenerator 生成密钥对象 key。* */Key key keyGenerator.generateKey();//返回生成的密钥的字节数组表示。return key.getEncoded();}/*** Description: 通过密钥生成器生成一个随机的 AES 密钥并转化为Base64格式** param* return java.lang.String*/public static String generateRandomAesKeyWithBase64(){return Base64.encode(generateRandomAesKey());}/* 当GET请求进行加密时地址上的加密参数就以16进制字符串的方式进行传输否则特殊符号路径无法解析[ 、/、]等Base64编码格式 *//*** Description: 从Byte[] 数组转 16进制字符串** param b* return java.lang.String*/public static String toHexString(byte[] b){/** 每个字节都可以用两个十六进制字符来表示因此初始化的容量是字节数组长度的两倍。* */StringBuilder sb new StringBuilder(b.length * 2);for (int i 0; ib.length ;i){/** 首先取字节的高四位然后查找对应的十六进制字符并将其追加到StringBuilder中* */sb.append(HEXCHAR[(b[i] 0xf0) 4]);/** 取字节的低四位找到对应的十六进制字符并追加到StringBuilder中。* */sb.append(HEXCHAR[b[i] 0x0f]);}return sb.toString();}/*** Description: 从16进制字符串转 byte[] 数组** param s* return byte[]*/public static final byte[] toBytes(String s){byte[] bytes;bytes new byte[s.length() / 2];for (int i 0; i bytes.length ; i){bytes[i] (byte) Integer.parseInt(s.substring(2*i,2*i2),16);}return bytes;} }AES加解密工具类方法代码解析为了方便自己理解和使用有必要简单分类记录一下工具类中的方法接口 AESUtil工具类中的方法封装的比较杂乱通过梳理之后更加能理清每个方法的具体用法和功能 生成AES密钥的方法: 该工具类中总共封装了两种 生成 AES密钥的方法 String getAESKey(int length) 和 String generateRandomAesKeyWithBase64() getAESKey 方法生成的密钥是 由数字(0-9)、小写字母、大写字母随机组成的普通字符串;generateRandomAesKeyWithBase64() 方法生成的密钥是 通过 **javax.crypto.KeyGenerator 密钥生成器 **生成Byte[] 类型的数据 在将该 byte[] 转换为 **Base64编码 **格式。 AES加密数据方法: AES工具类封装的加密数据方法有以下几种  byte[] encrypt(byte[] data,byte[] aesKey)、 String encryptToBase64(String data,String aeskey) 和  String encryptWithKeyBase64(String data,String aesKey) 共三种加密方式 byte[] encrypt(byte[] data,byte[] aesKey) 该AES加密方法为最基础的加密方式需要传入加密数据的 byte[] 格式数据以及 AES密钥的byte[] 格式数据。加密之后的数据会以 byte[]格式返回。 注其实其余加密方法都是基于该方法进行封装的。也可以根据自己需求来调整注意区别在于传入的数据格式有所区别 String encryptToBase64(String data,String aeskey) 该AES加密方法是通过传入加密数据的字符串同时传入字符格式的AES密钥Key(通过getAESKey生成的密钥) 方法内部会将传入进来的 待加密数据 data 和 aesKey密钥转换为 byte[] 格式然后在调用第一种加密方法最后生成的加密数据byte[] 也会在内部自动转换为Base64编码格式 然后返回。 String encryptWithKeyBase64(String data,String aesKey) 该AES加密方法需要传入 字符串形式的加密数据以及 **Base64编码格式的AES密钥 (**该密钥主要是通过generateRandomAesKeyWithBase64() 方法生成的密钥数据 为Base64编码格式 **)。**加密方法内部在接收到 待加密数据后会自动转换为byte[]格式在接收到Base64编码格式的AES密钥后,通过Base64.decode() 将其解码为 byte[]。然后在调用原始的加密方法对待加密数据进行加密操作。最终加密后的数据byte[] 通过 new String(valueByte,“UTF-8”) 的方式转换为字符串返回。 AES解密数据方法 AES工具类中封装的解密方法对应于加密方法byte[] decrypt(byte[] data,byte[] aesKey)、 String decryptFromBase64(String data,String aeskey) 和 String decryptFromBase64(String data,String aeskey)。三种方式。 该三种方式分别与上面三种加密方式是对应的。需要注意传入的数据封装格式就行。 第一种解密方法就需要传入 加密后数据格式 byte[]密钥格式 byte[]第二种解密方法对应于上面的第二种加密方法。需要传入的加密数据为Base64编码格式( 通过加密方法生成byte[] 后 在转换为 Base64格式 )需要传入AES密钥格式就为普通字符串格式(通过 String getAESKey(int length) 方法生成的密钥)。第三种解密方法需要传入的待解密数据 为Base64编码格式需要传入的AES密钥也为Base64编码格式 String toHexString(byte[] b) 方法 该方法是将byte[] 字节数据转换为16进制的字符串数据后续会利用到。 比如在Get请求种传输加密数据如果前端加密后的数据需要放入地址中进行传输到后端若采用Base64编码格式数加密数据进行传输时加密内容会包含 、\、 三个符号无法在地址中进行传输了。所有这里封装了该方法通过调用该方法将加密后的byte[] 字节数据数据转换为 HexString 16进制字符串格式(只包含了 0~9、a、b、c、d、e、f)。这样Get请求中的加密数据就可以通过地址进行传输了 byte[] toBytes(String s) 方法 该方法于 toHexString 方法相对应将转换为HexString十六进制的字符串 还原为字节数据Byte[]。 1.3 RSA加密工具类创建 RSA加密工具类同样引用了 hutool-all 依赖工具类。 package com.example.api_security_demo.utils;import cn.hutool.core.codec.Base64Encoder;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; import java.util.*;/*** ClassName : RSAUtil* Description : RSA加密工具类* Author : AD*/ public class RSAUtil {/*** SHA256withRSA 是一种使用 SHA-256 哈希算法和 RSA 加密算法结合的数字签名算法。* 在这种算法中数据首先会通过 SHA-256 进行哈希处理得到一个固定长度的摘要然后使用 RSA 私钥对这个摘要进行加密从而生成数字签名。* */public static final String ALGORITHM_SHA256WITHRSA SHA256withRSA;public static final String KEY_ALGORITHM RSA;//RSA最大加密明文大小public static final int MAX_ENCRYPT_BLOCK 117;//RSA最大解密密文大小public static final int MAX_DECRYPT_BLOCK 128;private static char[] HEXCHAR { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f };/*** Description: 公钥分段加密** param data 待加密源数据* param publicKey 公钥BASE64编码* param length 段长 1024长度的公钥最大取117* return byte[]*/public static byte[] encryptByPublicKey(byte[] data,String publicKey,int length) throws Exception{/** 将BASE64编码格式 publicKey进行解码* */byte[] publicKeyByte decryptBASE64(publicKey);/** 使用X509EncodedKeySpec类创建了一个X.509编码的KeySpec对象并将publicKeyByte作为参数传入。* 将公钥 [字符串] 解码成 [公钥对象] 以便用于加密数据。* */X509EncodedKeySpec x509EncodedKeySpec new X509EncodedKeySpec(publicKeyByte);/** 通过KeyFactory获取了RSA的实例* */KeyFactory keyFactory KeyFactory.getInstance(KEY_ALGORITHM);/** 调用generatePublic方法使用之前创建的X509EncodedKeySpec对象来生成公钥。* */Key generatePublicKey keyFactory.generatePublic(x509EncodedKeySpec);/** 创建一个Cipher实例它是用于加密或解密数据的对象。Cipher类提供了加密和解密功能并支持许多不同的加密算法。* 在这里getInstance 方法中传入了keyFactory.getAlgorithm()[获取与指定密钥工厂相关联的算法名称。]它用于获取与指定算法关联的 Cipher 实例。* */Cipher cipher Cipher.getInstance(keyFactory.getAlgorithm());/** 初始化 Cipher 对象。* 在初始化过程中指定加密模式为 ENCRYPT_MODE并传入了之前生成的公钥 generatePublicKey。* */cipher.init(Cipher.ENCRYPT_MODE,generatePublicKey);int inputLen data.length;ByteArrayOutputStream out new ByteArrayOutputStream();//段落起始位置int offSet 0;byte[] cache;int i 0;//对数据进行分段加密while (inputLen - offSet 0){if (inputLen - offSet length) {cache cipher.doFinal(data,offSet,length);} else {cache cipher.doFinal(data,offSet,inputLen-offSet);}out.write(cache,0,cache.length);i;offSet i * length;}byte[] encryptDate out.toByteArray();out.close();return encryptDate;}/*** Description:** param data 待解密数据* param privateKey 私密BUSE64编码* param length 分段解密长度 128* return byte[]*/public static byte[] decryptByPrivateKey(byte[] data,String privateKey,int length) throws Exception{byte[] privateKeyByte decryptBASE64(privateKey);PKCS8EncodedKeySpec pkcs8EncodedKeySpec new PKCS8EncodedKeySpec(privateKeyByte);KeyFactory keyFactory KeyFactory.getInstance(KEY_ALGORITHM);Key generatePrivateKey keyFactory.generatePrivate(pkcs8EncodedKeySpec);Cipher cipher Cipher.getInstance(keyFactory.getAlgorithm());cipher.init(Cipher.DECRYPT_MODE,generatePrivateKey);int inputLen data.length;ByteArrayOutputStream out new ByteArrayOutputStream();int offSet 0;byte[] cache;int i 0;//对数据进行分段解密while (inputLen - offSet 0){if (inputLen - offSet length){cache cipher.doFinal(data,offSet,length);} else {cache cipher.doFinal(data,offSet,inputLen - offSet);}out.write(cache,0,cache.length);i;offSet i * length;}byte[] decryptData out.toByteArray();out.close();return decryptData;}/*** Description: BASE64解码** param src* return byte[]*/public static byte[] decryptBASE64(String src){sun.misc.BASE64Decoder decoder new sun.misc.BASE64Decoder();try{return decoder.decodeBuffer(src);}catch (Exception ex){return null;}}/*** Description: BASE64编码** param src* return java.lang.String*/public static String encryptBASE64(byte[] src){sun.misc.BASE64Encoder encoder new sun.misc.BASE64Encoder();return encoder.encode(src);}/*** Description: 从Byte[] 数组转 16进制字符串** param b* return java.lang.String*/public static String toHexString(byte[] b){/** 每个字节都可以用两个十六进制字符来表示因此初始化的容量是字节数组长度的两倍。* */StringBuilder sb new StringBuilder(b.length * 2);for (int i 0; ib.length ;i){/** 首先取字节的高四位然后查找对应的十六进制字符并将其追加到StringBuilder中* */sb.append(HEXCHAR[(b[i] 0xf0) 4]);/** 取字节的低四位找到对应的十六进制字符并追加到StringBuilder中。* */sb.append(HEXCHAR[b[i] 0x0f]);}return sb.toString();}/*** Description: 从16进制字符串转 byte[] 数组** param s* return byte[]*/public static final byte[] toBytes(String s){byte[] bytes;bytes new byte[s.length() / 2];for (int i 0; i bytes.length ; i){bytes[i] (byte) Integer.parseInt(s.substring(2*i,2*i2),16);}return bytes;}/*** Description: 判断对象是否为null*/public static boolean isEmpty(Object str) {return (str null || .equals(str));}/*** RSA 公钥私钥生成器* */public static Map generateRandomToBase64Key() throws Exception{String KEY_ALGORITHM RSA;KeyPairGenerator keyPairGenerator KeyPairGenerator.getInstance(KEY_ALGORITHM);//密钥位数keyPairGenerator.initialize(1024);//创建公钥/私钥KeyPair keyPair keyPairGenerator.generateKeyPair();RSAPublicKey publicKey (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey (RSAPrivateKey) keyPair.getPrivate();byte[] publicKeyEncoded publicKey.getEncoded();byte[] privateKeyEncoded privateKey.getEncoded();HashMapString,String map new HashMap();map.put(publicKey, Base64Encoder.encode(publicKeyEncoded));map.put(privateKey,Base64Encoder.encode(privateKeyEncoded));return map;}/*** Description: 根据请求参数Map集合排号顺序Sort组装生成对应请求中的签名参数sign** param map 请求参数Map集合* param allowValueNull 是否允许map中的值为null true允许若允许为空则会出现ab* return java.lang.String*/public static String generateSortSign(MapString,String map,boolean allowValueNull){ListString keys new ArrayList(map.size());for ( String key : map.keySet() ){/** 排除下列参数数据* 1.不允许出现空value 且 map中为null 的键值对* 2.参数签名内容键值对* */if ( (!allowValueNull isEmpty(map.get(key))) || sign.equals(key) || signValue.equals(key) ){continue;}keys.add(key);}/** sort静态方法用于按自然顺序或自定义顺序对List进行排序* */Collections.sort(keys);StringBuffer stringBuffer new StringBuffer();boolean isFirst true;for (String key : keys){if (isFirst){stringBuffer.append(key).append().append(map.get(key));isFirst false;continue;}stringBuffer.append().append(key).append().append(map.get(key));}return stringBuffer.toString();}/*** Description: 用于生成数据的数字签名并将签名数据转换为十六进制字符串格式返回** param rawDate 签名裸数据* param privateKey 私钥* param algorithm 签名验算算法* return java.lang.String*/public static String generateSign(byte[] rawDate,String privateKey,String algorithm) throws Exception{byte[] privateKeyBytes decryptBASE64(privateKey);PKCS8EncodedKeySpec pkcs8EncodedKeySpec new PKCS8EncodedKeySpec(privateKeyBytes);KeyFactory keyFactory KeyFactory.getInstance(KEY_ALGORITHM);PrivateKey generatePrivate keyFactory.generatePrivate(pkcs8EncodedKeySpec);/** 使用指定的签名算法algorithm通过Signature实例获取签名对象signature。* */Signature signature Signature.getInstance(algorithm);/** 初始化签名对象传入生成的私钥generatePrivate。* */signature.initSign(generatePrivate);/** 将要签名的裸数据rawData传入签名对象。* */signature.update(rawDate);/** 生成签名数据sign* */byte[] sign signature.sign();return toHexString(sign);}/*** Description: 验证签名** param data 请求数据* param publicKey 公钥* param sign 签名数据* param algorithm 签名验算算法* return boolean*/public static boolean verify(byte[] data,String publicKey,String sign,String algorithm) throws Exception{byte[] publicKeyBytes decryptBASE64(publicKey);X509EncodedKeySpec x509EncodedKeySpec new X509EncodedKeySpec(publicKeyBytes);KeyFactory keyFactory KeyFactory.getInstance(KEY_ALGORITHM);PublicKey generatePublicKey keyFactory.generatePublic(x509EncodedKeySpec);Signature signature Signature.getInstance(algorithm);signature.initVerify(generatePublicKey);signature.update(data);return signature.verify( toBytes(sign) );} }RSAUtil加解密工具类方法代码解析为了方便自己理解和使用有必要简单分类记录一下工具类中的方法接口 生成密钥对方法Map generateRandomToBase64Key()byte[] 与 Base64编码互相转换的方法 byte[] decryptBASE64(String src) String encryptBASE64(byte[] src) HexString 十六进制字符串 与 byte[] 字节数组 互相转换的方法 String toHexString(byte[] b) byte[] toBytes(String s) RSA加密/解密方法 byte[] encryptByPublicKey(byte[] data,String publicKey,int length) RSAUtil工具类中的加密接口就只有 一个最终加密后的数据会以字节数组 byte[] 格式返回。最终用户想将密文以什么形式传输都可以( String() 字符串形式、Base64编码格式、HexString十六进制字符串形式 )。 byte[] decryptByPrivateKey(byte[] data,String privateKey,int length) 在解码数据时必须将密文数据根据对应的数据格式转换为 byte[] 后传入解码方法。 Base64格式编码解码方法 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中是获取不到参数的 自定义 RequestWrapper对象 package com.example.api_security_demo.common.core.wrapper;import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.web.multipart.MultipartRequest;import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.Charset; import java.util.*;/*** ClassName : RequestWrapper* Description : 自定义Request解决request请求流中的数据二次或多次使用问题* 继承HttpServletRequestWrapper将请求体中的流copy一份覆写getInputStream()和getReader()方法供外部使用。* 每次调用覆写后的getInputStream()方法都是从复制出来的二进制数组中进行获取这个二进制数组在对象存在期间一直存在这样就实现了流的重复读取。* Author : AD*/ Slf4j public class RequestWrapper extends HttpServletRequestWrapper {/*** 存储Body数据* */private byte[] body;///*** 保存原始Request对象当请求为 MultipartRequest 文件上传类的请求操作*/private HttpServletRequest request;/*** 额外参数可以加到这个里面 重写getParameter() 方法 从而使请求中不存在的参数通过该Map集合中获取*/private MapString, String[] parameterMap new LinkedHashMap();/*** Description: requestWrapper 请求包装类的构造方法** param request* return*/public RequestWrapper(HttpServletRequest request)throws IOException{super(request);//[文件上传相关的操作]this.request request;if(request instanceof MultipartRequest){// 如果是[文件上传类]请求this.parseBody(request);}else{//[普通请求类]将Body数据存储起来String bodyString getBodyString(request);body bodyString.getBytes(Charset.defaultCharset());}}/* [MultipartRequest 文件上传]相关操作接口 *//*** 如果是 MultipartRequest需要解析参数信息*/private void parseBody(HttpServletRequest request) {MapString,Object parameterMap new LinkedHashMap();EnumerationString parameterNames request.getParameterNames();while(parameterNames.hasMoreElements()){String name parameterNames.nextElement();String[] values request.getParameterValues(name);parameterMap.put(name, (values !null values.length 1) ? values[0] : values);}// 将解析出来的参数转换成JSON并设置到body中保存this.body JSONObject.toJSONString(parameterMap).getBytes(Charset.defaultCharset());}public void setBody(byte[] body){this.body body;try {if(this.request instanceof MultipartRequest){//[文件上传请求相关的操作]//todo 将Json格式body数据转换为mp//this.setParameterMap(JsonUtil.json2map(body));String bodyStr new String(body, UTF-8);com.fasterxml.jackson.databind.ObjectMapper objectMapper new com.fasterxml.jackson.databind.ObjectMapper();MapString, Object bodyMap objectMapper.readValue(bodyStr, Map.class);this.setParameterMap(bodyMap);}} catch (Exception e) {log.error(转换参数异常参数{}异常{},body, e);}}/*** Description:读取请全体Body中数据 [从 requestWrapper中读取]** param* return java.lang.String*/public String getBodyString(){final InputStream inputStream new ByteArrayInputStream(body);return inputStreamToString(inputStream);}/*** Description: 读取请求体Body中数据 [从HttpServletRequest中读取]** param request* return java.lang.String*/public String getBodyString(final ServletRequest request){try {return inputStreamToString(request.getInputStream());}catch (IOException e){log.error(Read Request Body IO_Stream Fail !,e);throw new RuntimeException(e);}}/*** Description: 将inputStream流里面的数据读取出来并转换为字符串形式** param inputStream* return java.lang.String*/private String inputStreamToString(InputStream inputStream){StringBuilder sb new StringBuilder();BufferedReader reader null;try {reader new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));String line;while ((line reader.readLine()) ! null){sb.append(line);}}catch (IOException e){log.error(BufferedReader is Fail !,e);throw new RuntimeException(e);}finally {if (reader ! null){try {reader.close();} catch (IOException e) {throw new RuntimeException(e);}}}return sb.toString();}Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream inputStream new ByteArrayInputStream(body);return new ServletInputStream() {Overridepublic boolean isFinished() {return false;}Overridepublic boolean isReady() {return false;}Overridepublic void setReadListener(ReadListener readListener) {}Overridepublic int read() throws IOException {return inputStream.read();}};}// [Get请求相关操作封装Request.getParameter()中的相关参数 ]/*** Description: The default behavior of this method is to return getParameter(Stringname) on the wrapped request object.** param name* return java.lang.String* date 2024-05-13*/Overridepublic String getParameter(String name) {String result super.getParameter(name);// 如果参数获取不到则尝试从参数(自定义封装的存贮零时请求数据的集合)Map中获取并且只返回第一个if(resultnull this.parameterMap.containsKey(name)){result this.parameterMap.get(name)[0];}return result;}/*** The default behavior of this method is to return getParameterMap() on the* wrapped request object.*/Overridepublic MapString, String[] getParameterMap() {// 需要将原有的参数加上新参数 返回MapString,String[] map new HashMap(super.getParameterMap());for(String key: this.parameterMap.keySet()){map.put(key, this.parameterMap.get(key));}return Collections.unmodifiableMap(map);}/*** The default behavior of this method is to return* getParameterValues(String name) on the wrapped request object.** param name*/Overridepublic String[] getParameterValues(String name) {String[] result super.getParameterValues(name);if(result null this.parameterMap.containsKey(name)){result this.parameterMap.get(name);}return result;}/*** The default behavior of this method is to return getParameterNames() on* the wrapped request object.*/Overridepublic EnumerationString getParameterNames() {EnumerationString parameterNames super.getParameterNames();SetString names new LinkedHashSet();if(parameterNames !null){while(parameterNames.hasMoreElements()){names.add(parameterNames.nextElement());}}// 添加后期设置的参数Mapif(!this.parameterMap.isEmpty()){names.addAll(this.parameterMap.keySet());}return Collections.enumeration(names);}/*** 设置参数map* param json2map*/public void setParameterMap(MapString, Object json2map) {if(json2map ! null !json2map.isEmpty()) {for (String key : json2map.keySet()){//获取map中对应key的valueObject value json2map.get(key);if(this.parameterMap.containsKey(key)){//如果额外参数HashLink中包含该参数则在赋值加入到String[] 中String[] originalArray this.parameterMap.get(key);int originalLength originalArray.length;originalArray Arrays.copyOf(originalArray,originalLength 1);originalArray[originalLength] String.valueOf(value);//this.parameterMap.put(key, Collection.add(this.parameterMap.get(key), value));this.parameterMap.put(key,originalArray);}else{this.parameterMap.put(key, new String[]{String.valueOf(value)});}}}}} RequestWrapper类方法代码解析为了方便自己理解和使用有必要简单分类记录一下工具类中的方法接口这里自定义封装的 RequestWrapper对象继承 HttpServletRequestWrapper。 通过重写 getReader() 、getInputStream() 等方法从而可以实现通过request对象读取body数据时能够直接获取到该对象中我们自己封装的用于存放请求体数据的属性byte[] body。**void setBody(byte[] body) **方法可以用来给自定义封装的属性body进行赋值在 解密请求数据的 FIlter中解密了body数据后通过调用该方法将解密后的body数据存入requestWrapper对象。这样在后续的业务操作中直接通过request对象获取到的body数据就是已经解密的数据。实现了业务无感知**void setParameterMap(MapString, Object json2map) ** 在解密Filter中将Get请求中地址上的加密数据进行解密之后调用该方法就将解密后的Get请求数据封装到 requestWrapper对象中的Map集合中实现后续调用request.getParameter() 方法时能够获取到解密后的参数。重写父类 getParameter(String name)、MapString, String[] getParameterMap()、 String[] getParameterValues(String name)等方法实习在getRequestParameter()数据时也能够在requestWrapper对象中的Map集合中获取参数数据。 2.2 过滤器Filter解密请求参数 这里封装的解密参数过滤器Filter中首先需要通过请求头中的 aksEncrypt数据判断该请求是否为加密请求如果不是则直接放行不做解密操作。如果需要解密的请求首先判断请求类型在进行对应的解密处理。 Filter解密数据过滤器代码 package com.example.api_security_demo.filter;import com.alibaba.fastjson2.JSONObject; import com.example.api_security_demo.common.core.wrapper.RequestWrapper; import com.example.api_security_demo.utils.AESUtil; import com.example.api_security_demo.utils.RSAUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.ObjectUtils;import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Map;/*** ClassName : DecryptReplaceStreamFilter* Description :Filter过滤器 解密请求参数。同时替换请求体使后续操作无感知加密* Author : AD*/Slf4j public class DecryptReplaceStreamFilter implements Filter {/** AKSAuthentication Key Management System采用无明文密钥的方式对数据进行加密保护加解密运算由统一的安全计算中心完成AKS系统以接口的方式为对各个业务线提供加密解服务。* 各个系统不再使用明文密钥只使用密钥别名调用简单的加解密函数完成对数据的保护。* *///请求头标签开关是否需要加密解密private static final String AKS_ENABLE aksEncrypt;//GET请求加密数据Keypublic static final String AKS_PARAMETER encryptData;private static final String METHOD_GET GET;private static final String METHOD_POST POST;//POST请求加密数据Keyprivate static final String AKS_BODY content;//AES密钥Keyprivate static final String AES_KEY aesKey;/*** Feign是声明式Web Service客户端它让微服务之间的调用变得更简单类似controller调用service。* Feign内部调用时请求头中标注请求源* */public static final String SOURCE_KEY api-source;public static final String SOURCE_VALUE inner-api;Value(${Rsa.PrivateKey})private String privateKey;Value(${API.Security.enable})private boolean securityEnable;Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {//转换为自己Wrapper,实现多次读写RequestWrapper requestWrapper new RequestWrapper((HttpServletRequest) servletRequest);String contentType requestWrapper.getContentType(); //请求头中获取Content-Type数据String requestURI requestWrapper.getRequestURI();//配置文件配置是否开启加解密功能if (!securityEnable){log.info(未开启接口安全加密传输! 无需解密请求参数);filterChain.doFilter(requestWrapper,servletResponse);return;}//通过请求头参数判断该请求是否需要解密处理if (!needAks(requestWrapper)){log.info(请求{}非加密请求无需解密操作,requestURI);filterChain.doFilter(requestWrapper,servletResponse);return;}/** [该功能暂时不用管因为在请求头中不添加 aksEncrypttrue 键值对请求接口时同样不会去进行解密请求数据的操作]* *///Feign服务端调用内部请求按照不加密的逻辑放行[Feign是声明式Web Service客户端它让微服务之间的调用变得更简单类似controller调用service。]//前端 -- A -- b -- cString sourceKey requestWrapper.getHeader(SOURCE_KEY);if (!ObjectUtils.isEmpty(sourceKey) sourceKey.equals(SOURCE_VALUE) ){log.info(内部请求无效加密解密接口数据);filterChain.doFilter(requestWrapper,servletResponse);return;}/** POST请求进行解密工作* */if (requestWrapper.getMethod().equalsIgnoreCase(METHOD_POST)){//读取JSON请求体数据StringBuffer bodyInfo new StringBuffer();String line null;BufferedReader reader null;reader requestWrapper.getReader();while ( ( line reader.readLine() ) ! null ){bodyInfo.append(line);}//解密请求体数据JSONObject jsonObject JSONObject.parseObject(bodyInfo.toString());log.info(Post请求:{}待解密请求参数{}, requestURI, JSONObject.toJSONString(jsonObject));//获取通过AES加密之后的密文String content jsonObject.getString(AKS_BODY);//获取通过RSA加密之后的AES密钥KEyString aesKey jsonObject.getString(AES_KEY);//RSAUtil解密出AES密钥Keytry {aesKey new String(RSAUtil.decryptByPrivateKey(RSAUtil.toBytes(aesKey),privateKey,RSAUtil.MAX_DECRYPT_BLOCK),UTF-8);} catch (Exception e) {throw new RuntimeException(e);}/** 方式1.将解密之后的数据aesKey [放入body中弃会影响body结构] ( 满足在AOP操作中对出参数据进行加密 ) 一并交给下游业务。*///JSONObject requestBody JSONObject.parseObject(data);//requestBody.put(AES_KEY,aesKey);/** 方式2.将其放入[请求对象属性中中]不影响请求体结构*///requestWrapper.setAttribute(AES_KEY,aesKey);/** 方式3.将其放入[放入RequestWrapper封装的额外参数Map中]不影响请求体结构* */HashMapString, Object map new HashMap();map.put(AES_KEY,aesKey);requestWrapper.setParameterMap(map);//AESUtil aesKey 解密json数据String decryptData AESUtil.decryptFromBase64(content, aesKey);log.info(Get请求:{}解密之后参数{}, requestURI, decryptData);//重置Json请求体保证下游业务无感知获取数据//requestWrapper.setBody(requestBody.toJSONString().getBytes());requestWrapper.setBody(decryptData.getBytes());}/** GET请求解密处理[AES密钥Key放置在请求头中]* */else if (requestWrapper.getMethod().equalsIgnoreCase(METHOD_GET)){//Get请求中 获取到指定加密的参数 然后进行解密操作String encryptData requestWrapper.getParameter(AKS_PARAMETER);log.info(Get请求:{}待解密请求参数{}, requestWrapper.getRequestURI(), encryptData);// 先解密 存放在请求头中且经过RSA加密过的AES密钥String aesKey requestWrapper.getHeader(AES_KEY);if (encryptData ! null !encryptData.isEmpty() aesKey ! null !aesKey.isEmpty()){try {byte[] aesKeyByte RSAUtil.decryptByPrivateKey(RSAUtil.toBytes(aesKey), privateKey, RSAUtil.MAX_DECRYPT_BLOCK);aesKey new String(aesKeyByte,UTF-8);System.out.println(aesKey.toString() aesKey.toString());} catch (Exception e) {throw new RuntimeException(e);}//Get请求中的加密数据是以16进制字符串方式传输byte[] encryptDataByte AESUtil.toBytes(encryptData);System.out.println(Arrays.toString(encryptDataByte) Arrays.toString(encryptDataByte));// 解密数据操作 AKS_PARAMETERString decryptData new String(AESUtil.decrypt(encryptDataByte,aesKey.getBytes(UTF-8)),UTF-8);log.info(Get请求:{}解密之后参数{}, requestURI, decryptData);//将GET请求中的Parameter参数赋值入RequestWrapper中封装的其它参数存放集合Map中com.fasterxml.jackson.databind.ObjectMapper objectMapper new com.fasterxml.jackson.databind.ObjectMapper();MapString, Object map objectMapper.readValue(decryptData, Map.class);//将aseKey数据也存入map额外参数中map.put(aesKey,aesKey);requestWrapper.setParameterMap(map);}}filterChain.doFilter(requestWrapper,servletResponse);}Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}Overridepublic void destroy() {Filter.super.destroy();}/*** Description: 判断当前请求是否需要加密解密数据** param request* return boolean*/private boolean needAks(HttpServletRequest request){String enableAKS request.getHeader(AKS_ENABLE);return (enableAKS ! null enableAKS.equalsIgnoreCase(true))? true: false;} } 代码解析 代码中通过三重方式放到是否需要解密请求接口。一个是配置文件中读取的开关配置、一个是通过请求头中是否开启加密传输的标识(也就是在发送加密接口时都需要在请求头上封装该开关)、Feign服务端调用内部请求按照不加密的逻辑放行。解密方式按照文章开头采用的方案先用RSA密钥解密出随机的AES密钥在通过密钥解密密文。对应Get请求和Post请求的解密方式两点区别。Post整个请求体都是加密参数即body数据通过Base64方式传输进来的Get请求中的加密参数需要使用encryptData由于需要放入路径中Base64编码格式中含有、\、等特殊符号无法进行传输和解析所以Get请求中的加密数据是采用HexString格式进行传输的在解密时也需要对应的方法进行转换为Byte[]后在进行解密操作。关于AES密钥的存放位置。Post请求中的AES密钥是存入body中存入过来的而Get请求中的AES密钥是存放在请求头中传递过来的所以在解密对应请求方式的密文时注意AES密钥的获取方式。关键解密后的AES密钥需要一并交给下游业务。满足在AOP操作中对出参数据进行加密所以解密出来的AES密钥也需要存入requestWrapper对象的Map集合中方便下游业务获取。 Filter过滤器注册配置类 需要注意解密过滤器Filter 的优先级一点要设置为最高优先级registration.setOrder(1);首先需要该过滤器对加密数据进行解密后在重新封装请求才能使后续的业务数据能直接获取到解密后的数据达到无感知的效果。 package com.example.api_security_demo.filter;import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;import javax.servlet.Filter;/*** ClassName : WebAllHandlerConfig* Description : 过滤器、监听器、拦截器 前置处理配置类* Author : AD*/Configuration public class WebAllHandlerConfig {/*** 注册过滤器* */Beanpublic FilterRegistrationBean AllFilterRegistration(){FilterRegistrationBeanFilter registration new FilterRegistrationBean();registration.setFilter(decryptReplaceStreamFilter());registration.addUrlPatterns(/*);registration.setName(APISecurityFilter);registration.setOrder(1);return registration;}Bean(name decryptReplaceStreamFilter)public Filter decryptReplaceStreamFilter(){return new DecryptReplaceStreamFilter();}} 三、响应数据加密实现方案 响应数据加密的实现方式是采用AOP来实现可以对返回的结果对象进行处理而Filter只能拿到Request与Response对象处理不方便 这里的响应数据加密方案就简单采用的是AES加密通过前端传来的随机AES进行加密响应数据后在响应给前端 这里响应加密的开启方式是通过自定义注解来实现的创建自定义一个自定义注解作为响应数据加密的切点就实现了响应数据是否加密的开启。 3.1 自定义注解开启加密的注解 package com.example.api_security_demo.common.core.annotation;import java.lang.annotation.*;/*** ClassName : ResponseEncrypt* Description : 响应数据加密开启注解* Author : AD*/Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) //Documented public interface ResponseEncrypt { } 3.2 AOP实现响应数据加密功能 需要注意这里的响应加密AOP的优先级也要设置为最高Order(value 0)从而使最先开始的AOP能最后结束这样才能保证 加密响应的AOP最终处理的响应数据是所以业务逻辑都处理结束后最终的响应结果然后进行加密处理后响应给前端。 package com.example.api_security_demo.aop;import com.example.api_security_demo.common.R; import com.example.api_security_demo.common.core.annotation.ResponseEncrypt; import com.example.api_security_demo.utils.AESUtil; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;/*** ClassName : ResponseEncryptAOP* Description : 传输加密模块AOP对接口的出参进行加密注意顺序不能乱* 此AOP必须第一个执行因为最先执行的最后结束这样才能在各个AOP都执行完毕之后完成最后的加密* Author : AD*/Order(value 0) Aspect Component public class ResponseEncryptAop {Pointcut(annotation(com.example.api_security_demo.common.core.annotation.ResponseEncrypt))public void point(){}/*** 环绕增强加密出参* */Around(value point() annotation(responseEncrypt))public Object aroundEncrypt(ProceedingJoinPoint joinPoint, ResponseEncrypt responseEncrypt) throws Throwable {//返回的结合Object returnValue null;//从上下文中提取HttpServletRequest request ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();HttpServletResponse response ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();//业务执行返回结果returnValue joinPoint.proceed();System.out.println(returnValue.toString() returnValue.toString());//获取到前端传递过来的AES密钥然后对响应数据进行AES加密操作String aesKey request.getParameter(aesKey);System.out.println(aesKey aesKey);String encryptToBase64Data AESUtil.encryptToBase64(returnValue.toString(), aesKey);returnValue R.ok(encryptToBase64Data);return returnValue;} }四、测试相关的类 4.1 测试类实体封装 package com.example.api_security_demo.controller;import lombok.Data; import lombok.ToString; import lombok.experimental.Accessors;/*** ClassName : TbStudenEntity* Description : 测试实体类* Author : AD*/Data Accessors(chain true) ToString public class TbStudentEntity {int id;String name;String sex;int age; }4.2 测试接口Controller层封装 package com.example.api_security_demo.controller;import com.example.api_security_demo.common.R; import com.example.api_security_demo.common.core.annotation.ResponseEncrypt; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;/*** ClassName : TbStudentController* Description : 前端控制器Controller测试类* Author : AD*/RestController RequestMapping(/demo) Slf4j public class TbStudentController {PostMapping(/encryptPost)ResponseEncryptpublic R postDemo(RequestBody TbStudentEntity tbStudentEntity,HttpServletRequest request){System.out.println(request.getParameter(\aesKey\) request.getParameter(aesKey));//System.out.println(request.getAttribute(\aesKey\) request.getAttribute(aesKey));System.out.println(tbStudentEntity tbStudentEntity);return R.ok(tbStudentEntity.toString());}GetMapping(/encryptGet)ResponseEncryptpublic R postDemoGet(HttpServletRequest request,RequestParam String id,RequestParam String name,RequestParam String aesKey ){System.out.println(id id);System.out.println(name name);System.out.println(aesKey aesKey);System.out.println(request.getParameter(\age\) request.getParameter(age));//System.out.println(request.getAttribute(DecryptReplaceStreamFilter.AKS_PARAMETER) request.getAttribute(DecryptReplaceStreamFilter.AKS_PARAMETER));return R.ok();} }4.3 前端模拟生成加密数据 package com.example.api_security_demo.utils;import com.alibaba.fastjson2.JSONObject; import com.fasterxml.jackson.core.JsonProcessingException; import lombok.extern.slf4j.Slf4j; import org.junit.Test;import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.HashMap; import java.util.Map;/*** ClassName : UtilsTestClass* Description : 工具类测试类* Author : AD*/Slf4j public class UtilsTestClass {/*** 可以将其放入配置文件中进行读取* */String publicKey MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI7HE0aW9ILCMkfoAJYmAG5RBRhU2Itebf04GUtnYMuR6Rl1GJKec7JKuM/8xSindH4jn6Vz3oARTjbCn4CxjbtQPys5i8VeXxgzzqhE34LY0Rt62Qy8UVS113454DTwZZR9BjmPQSxMaftQHMgeDjXVwLUt0a6CmRiZKOjw8WQIDAQAB;String privateKey MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMjscTRpb0gsIyRgAliYAb7lEFGFTYi15t/TgZS2dgy5HpGXUYkp5zskq4z/zFKKd0fiOfpXPegBFONsKfgLGNu1A/KzmLxV5fGDPOqETfgtjRG3rZDLxRVLXXfjngNPBllH0GOY9BLExp1AcyB4ONdXAtS3RroKZGJko6PDxZAgMBAAECgYBRUHdkKcNypwI188gnhBuu18QhQpa1CRbPBI90ObWWLMqQvcdj6tO2y3t1au9Z/FXXzrNIC6apU1p2M2HaB4iPdW2Vm0DaRN7XJPzBdJqVASYVJ2oWWLWCkG05mS2pAgcMlxV3TLen7iBFKTjgZhdLIal8JYgyi6XWdNlAtAQJBAPjCtpmXuAS9luyXo2ExEauKEC2uC06FeZqgM6u1rTqsqUE5kGpmJ1DiERuOcQxn8imlnJ1Urmw8ltMxmnJAiECQQDOxVbjUXWolwRF8lsOeZ9XGGc/45pE7J8DubEV0Ii1RVckMyTkXmgNAupE7Xq9cgNMpNBRUQEDNzSHhA8rqGM5AkBuwWrBac6RtcPLpRwls3uPTNE01fPZxgkYy1Rw5QsG1PUAzfgooAthZ92Wa16lXnJ1mWrmvdp03Qnpc8pDVhAkBoqWD6vV/2D0L9eNh4cj2iY1rX7whGfRNcWmD1rtGUF94tF6pD4jl5Ivaie6HC8NW5uKnZsKmqX/NmxLZ/eZAkBDXRX7YgMG4PLXcjPT4ot6DEJDjuSVb1LhwucL6QFFYhZloH3/9XQS3T9XB/2F61tdh5Jd3FaCD7WeXJCzW64;/*** Description: 测试获取密钥的方法*/Testpublic void testGetASK() throws Exception {// AES密钥String aesKey AESUtil.getAESKey(16);System.out.println(aesKey aesKey);// AES Base64类型密钥String base64AesKey AESUtil.generateRandomAesKeyWithBase64();System.out.println(base64AesKey base64AesKey);//RSA 私钥公钥Map map RSAUtil.generateRandomToBase64Key();System.out.println(map.get(\privateKey\) map.get(privateKey));System.out.println(map.get(\publicKey\) map.get(publicKey));}/*** Description: post请求加密传输请求体body封装测试** param* return void*/Testpublic void postRequestBodyEncryptTest() throws Exception {//自定义方式获取 aesKeyString aesKey AESUtil.getAESKey(16);//内部方式获取 Base64AesKeyString base64AesKey AESUtil.generateRandomAesKeyWithBase64();//创建请求体body数据 content//模拟业务数据Json,并用AES密钥Key进行加密MapString, Object dataMap new HashMap();dataMap.put(id, 15);dataMap.put(name, 张三);dataMap.put(sex, 男);dataMap.put(age, 20);JSONObject dataJson new JSONObject(dataMap);String contentJson JSONObject.toJSONString(dataJson);//AES 加密业务数据String content AESUtil.encryptToBase64(contentJson, aesKey);log.info(AES加密数据操作 \nAES加密之前的数据 {} \nAES加密之后的数据 content {},contentJson,content);//RSA 加密AES密钥byte[] bytes RSAUtil.encryptByPublicKey(aesKey.getBytes(UTF-8), publicKey, RSAUtil.MAX_ENCRYPT_BLOCK);String encryptAesKey RSAUtil.toHexString(bytes);log.info(RSA加密AES密钥操作 \nRSA加密之前的AES密钥 {} \nRSA加密之后的AES密钥 aesKey{},aesKey,encryptAesKey);/*** 09:08:42.376 [main] INFO com.example.api_security_demo.utils.UtilsTestClass - AES加密数据操作* AES加密之前的数据 {sex:男,name:张三,id:15,age:20}* AES加密之后的数据 content u/vRpwktLAo12ATyfa1rb14EHNftHfvhYEfy7rDOJzO6jzXS4bwUJ0xNY8RJu8f* 09:08:42.380 [main] INFO com.example.api_security_demo.utils.UtilsTestClass - RSA加密AES密钥操作* RSA加密之前的AES密钥 9raG41FIE8uK7l3k* RSA加密之后的AES密钥 aesKeyc7eccef1d112075ee64eec65163b8b1dcb1a54ea6c8b51875174f6d34fc4ac7d50d2977b7519d275ee610d717d594228e132b053a70cdad9f925701a728ed794684d12097cfb8bea598c561393cb69de384b2ec83aa8ddb9a98a5adb3ed51ee1b9aaab2cf7bc5b49712a95e40ac4ea17421f5250d34b8e629e58db0b26e54b39* */}/*** Description: Get请求加密传输请求体RequestParameter数据封装测试* 注意: 通过AES加密之后的数据 byte[] 需要转换为 HexString十六进制字符串后才能在 请求地址中传输不能在用Base64编码格式的方式进行传输*/Testpublic void getRequestBodyEncryptTest() throws Exception {//自定义方式获取 aesKeyString aesKey AESUtil.getAESKey(16);//内部方式获取 Base64AesKeyString base64AesKey AESUtil.generateRandomAesKeyWithBase64();//创建请求体body数据 content//模拟业务数据Json,并用AES密钥Key进行加密MapString, Object dataMap new HashMap();dataMap.put(id, 15);dataMap.put(name, 张三);dataMap.put(sex, 男);dataMap.put(age, 20);JSONObject dataJson new JSONObject(dataMap);String contentJson JSONObject.toJSONString(dataJson);//AES 加密业务数据byte[] contentByte AESUtil.encrypt(contentJson.getBytes(UTF-8), aesKey.getBytes(UTF-8));String content AESUtil.toHexString(contentByte);log.info(AES加密数据操作 \nAES加密之前的数据 {} \nAES加密之后的Byte[]数据 {} \nAES加密后的数据转换为HexString十六进制字符串数据encryptData{},contentJson,Arrays.toString(contentByte),content);//RSA 加密AES密钥byte[] bytes RSAUtil.encryptByPublicKey(aesKey.getBytes(UTF-8), publicKey, RSAUtil.MAX_ENCRYPT_BLOCK);String encryptAesKey RSAUtil.toHexString(bytes);log.info(RSA加密AES密钥操作 \nRSA加密之前的AES密钥 {} \nRSA加密之后的AES密钥aesKey{},aesKey,encryptAesKey);/*** 09:09:13.172 [main] INFO com.example.api_security_demo.utils.UtilsTestClass - AES加密数据操作* AES加密之前的数据 {sex:男,name:张三,id:15,age:20}* 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]* AES加密后的数据转换为HexString十六进制字符串数据encryptData49136b3c1d6d6477af8bacab131c56127b303a25e4bfa384ce59f69b303098ee937fed503e86b0865e48f0a7ff8016a4* 09:09:13.177 [main] INFO com.example.api_security_demo.utils.UtilsTestClass - RSA加密AES密钥操作* RSA加密之前的AES密钥 868E9FMA727S9W5q* RSA加密之后的AES密钥aesKey2ab5531c7814201b4eaef3802ca883e79ffffd4c4ec32e698403189c0954718fd5cebd0ac5e66e856ec4f95a408442fc76276586a8fdb94c14c8f311f30ad061d6928315078736e6633113cdba255870a78e9077b2f18bdc4b2730804e5d6181df4b0ecf51597f71c8e0ccb89a5e160f1216a7bde5386b42171577db400d5a54* */}Testpublic void testByteBase64String(){/** 创建字节数组的方式* */byte[] testBytes new byte[]{0,1,2,3,4};/** 读取字节数组的方式* */System.out.println(Arrays.toString(bytes1) Arrays.toString(testBytes));//模拟业务数据Json,并用AES密钥Key进行加密MapString, String dataMap new HashMap(2);dataMap.put(name, 张三);dataMap.put(age, 20);JSONObject dataJson new JSONObject(dataMap);String jsonStr JSONObject.toJSONString(dataJson);com.fasterxml.jackson.databind.ObjectMapper objectMapper new com.fasterxml.jackson.databind.ObjectMapper();try {MapString, Object map objectMapper.readValue(jsonStr, Map.class);System.out.println(map.toString() map.toString());System.out.println(map.get(\name\) map.get(name));System.out.println(map.get(\age\) map.get(age));} catch (JsonProcessingException e) {throw new RuntimeException(e);}byte[] jsonByte dataJson.toString().getBytes();System.out.println(new String(jsonByte) new String(jsonByte));//[两种方式不一样]String hexStr 0123456789abcdef;System.out.println(Arrays.toString(hexStr.getBytes()) Arrays.toString(hexStr.getBytes()));System.out.println(Arrays.toString(RSAUtil.toBytes(hexStr)) Arrays.toString(RSAUtil.toBytes(hexStr)));}/*** Description: 解密操作测试*/Testpublic void testAesDecrypt() throws Exception {String encryptAesKey 868dbf00b6849a1189f186f18bc98eca65981829e12d3bad21f4a64c139a6fe6953729e488af642cb5bf7104459a4fbf084bb536f251e2d9fa39747037715da6a73caf23e1d68bd5338d51dd207ebe9c4a72749d87d73eb96fc193adac45e6b8b6b7fcc211ee47efd0d54ea97dcfdbc221ac0bd7664d32becbdd654c3d9b2446;String encryptDate 8ee240b806a571f0f7ef5568d9cf5e36d999686acabfa4d5425d73ef7e546c8c1e9147c084269a6884cfeebf6759bd60;//解密之后的AesKey 56X8817GRC2p33w0byte[] decryptAesKey RSAUtil.decryptByPrivateKey(RSAUtil.toBytes(encryptAesKey), privateKey, RSAUtil.MAX_DECRYPT_BLOCK);String decryptAesKeyStr new String(decryptAesKey, UTF-8);System.out.println(decryptAesKeyStr decryptAesKeyStr);//GET请求数据解密 [加密数据为HexStr十六进制字符串形式]byte[] encryptDataByte AESUtil.toBytes(encryptDate);System.out.println(Arrays.toString(encryptDataByte) 加密数据Byte[]形式 Arrays.toString(encryptDataByte));String decryptData new String(AESUtil.decrypt(encryptDataByte,decryptAesKeyStr.getBytes(UTF-8)),UTF-8);System.out.println(decryptData解密后的数据 decryptData);/* //POST请求数据解密 [加密数据为Base64编码格式]String decryptData2 AESUtil.decryptFromBase64(encryptDate, decryptAesKeyStr);System.out.println(decryptData2 解密后的数据 decryptData2);*//*** AES加密之前的数据 {sex:男,name:张三,id:15,age:20}* 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]* AES加密后的数据转换为HexString十六进制字符串数据encryptData8ee240b806a571f0f7ef5568d9cf5e36d999686acabfa4d5425d73ef7e546c8c1e9147c084269a6884cfeebf6759bd60* 16:48:25.095 [main] INFO com.example.api_security_demo.utils.UtilsTestClass - RSA加密AES密钥操作* RSA加密之前的AES密钥 8c0N9032214LJ139* RSA加密之后的AES密钥aesKey868dbf00b6849a1189f186f18bc98eca65981829e12d3bad21f4a64c139a6fe6953729e488af642cb5bf7104459a4fbf084bb536f251e2d9fa39747037715da6a73caf23e1d68bd5338d51dd207ebe9c4a72749d87d73eb96fc193adac45e6b8b6b7fcc211ee47efd0d54ea97dcfdbc221ac0bd7664d32becbdd654c3d9b2446* */}/*** Description: 响应数据加密后模拟前端进行解密操作*/Testpublic void responseEncryptToDecrypt() throws UnsupportedEncodingException {String aesKeyPost 9raG41FIE8uK7l3k;String aesKeyGet 0303F71572405EF1;String encryptData cf156ddc9cd9fde2a8287fa3b8eadca258ce063130d29d7d9c1b949a4628c42e497b4e1db76244bdd075cb37b8ef0212;//解密后的数据String decryptData ;//Base64编码格式的数据解密//decryptData AESUtil.decryptFromBase64(encryptData, aesKeyGet);//HexString格式的数据转换后在解密byte[] decrypt AESUtil.decrypt(AESUtil.toBytes(encryptData), aesKeyGet.getBytes(UTF-8));decryptData new String(decrypt, UTF-8);System.out.println(decryptData decryptData);} }4.4 模拟加密请求调用接口 4.4.1 Post请求的封装 请求头中封装数据 aksEncrypt: true 开启解密功能 请求体Body(application/json格式)的封装 content通过随机AES密钥加密后的请求数据 aesKey: 通过RSA加密后的随机AES密钥 Post请求后响应数据 可以看出 请求数据解密后通过 RequestWrapper重新封装后的请求数据能直接通过RequestBody 等注解直接获取到相请求体中的数据。 同时也可以通过 request.getParameter()中存放的参数。 4.4.2 Get请求的封装 Get请求的封装 Get请求响应结果 可以看出 请求数据解密后通过 RequestWrapper重新封装后的请求数据能直接通过RequestParam等注解直接获取到Get请求中的数据。 同时也可以通过 request.getParameter()中存放的参数。 [结束]还有些地方封装的不是很恰当欢迎各位大佬留言提意见
http://www.w-s-a.com/news/427572/

相关文章:

  • 上海营销网站建设公司下面哪个不是网页制作工具
  • 有哪些网站可以做设计比赛苏州设计公司排名前十
  • 公益网站建设需求车陂手机网站开发
  • 高端网站建设专业营销团队宁德网站建设51yunsou
  • 网站如何做cdn购物网站建设app开发
  • 简单的手机网站模板好看大方的企业网站源码.net
  • 沈阳住房和城乡建设厅网站网站个人备案做论坛
  • 企业建网站的目的开家网站建设培训班
  • 做怎么网站网站优化和推广
  • 建站工具 风铃网站每年空间域名费用及维护费
  • 网站开发工具 知乎工业软件开发技术就业前景
  • 永济微网站建设费用新手如何自学编程
  • 在本地怎么做网站深圳保障房申请条件2022
  • 广州天河区网站建设公司东莞网络游戏制作开发
  • 哪个网站做免费小程序rio门户网站的制作
  • 短网站生成查询网站所有关键词排名
  • 阿里云购买网站登录技术服务外包公司
  • 淘宝单页面网站手机制作游戏的软件
  • 汉中市网站建设wordpress编辑器好麻烦
  • 织梦做的网站快照被攻击在线看crm系统
  • 青岛物流公司网站建设网站建设提议
  • 企业网站建设高端品牌宿州注册公司多少钱
  • 个人微信公众号怎么做微网站吗湛江网站制作方案
  • 学校网站改版南京展厅设计装修
  • 手机网站有免费做的吗建设银行网站不能登录
  • 树莓派做影视网站网站建设企业 熊账号
  • 网站iis7.5配置免费网站建设模板下载
  • 生物公司网站建设方案wordpress自定义字段调用
  • 静态网站公用头部如何调用标题wordpress自动采集翻译插件怎么用
  • 网站做单链 好不好网站营销不同阶段的网站分析目标