美文网首页javaWeb学习61ce5288627f简书-架构专栏
RSA+AES实现接口验签和参数加密

RSA+AES实现接口验签和参数加密

作者: Longer_JzL | 来源:发表于2020-08-30 15:32 被阅读0次

    RSA非对称加密

    RSA是一种常用的非对称加密算法,加密和加密使用不同的密钥,常用于要求安全性较高的加密场景,比如接口的验签和接口数据的加密与解密。与非对称加密算法对比,其安全性较高,但是加密性能却比较低,不适合高并发场景,一般只加密少量的数据。

    AES对称加密

    AES是一种最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的),加密和解密使用的是相同的密钥。其加密性能好,加密解密速度非常快,内存需求低,适用于经常发送数据的场合。

    RSA+AES实现接口验签和请求参数的加密与解密

    背景:做为程序猿,我们经常需要在我们自己开发的系统上,开发一些接口供第三方调用,那么这个时候,对我们接口的安全性要求就比较高了,尤其是那种需要传输比较私密的信息的时候,其对安全性的要求就更高了。

    实现思路

    调用方:

    • 使用AES对称加密算法对业务请求参数进行加密后传输
    • 使用RSA非对称加密算法对AES的密钥进行公钥加密后传输
    • 使用RSA的私钥对请求参数进行签名

    接收方:

    • 获取到请求参数后,对参数进行验签和业务参数的解密

    问题:为什么要对AES的密钥进行RSA公钥加密后传输?

    AES是对称加密算法,加密和解密的密钥都是同一个,为了防止被别人恶意获取到该密钥,然后对我们的业务请求参数进行解密,我们需要将AES密钥进行非对称加密后再进行传输。

    代码实现

            <!--RSA依赖-->
            <dependency>
                <groupId>org.bouncycastle</groupId>
                <artifactId>bcpkix-jdk15on</artifactId>
                <version>1.56</version>
            </dependency>
    
            <!--Jackson依赖-->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.9.8</version>
            </dependency>
            <!--Jackson依赖-->
            <dependency>
                <groupId>org.codehaus.jackson</groupId>
                <artifactId>jackson-core-asl</artifactId>
                <version>1.8.3</version>
            </dependency>
    
    请求和接收的实体对象
    @Data
    public class JsonRequest {
        //接口id 可空
        private String serviceId;
        //请求唯一id 非空
        private String requestId;
        //商户id 非空
        private String appId;
        //参数签名 非空
        private String sign;
        //对称加密key 非空
        private String aseKey;
        //时间戳,精确到毫秒 非空
        private long timestamp;
        //请求的业务参数(AES加密后传入) 可空
        private String body;
    }
    
    • serviceId:服务id(接口id)。接口设计分为两种,一种是所有的调用方针对类似的业务,都调用的是同一接口地址,然后内部系统根据serviceId去判断具体是要调用哪个业务方法;另一种是针对不同的调用方,开发不同的接口,接口地址也是不一样,那么这个时候就可以不要serviceId这个字段。本章是使用第二种方式,所以serviceId可以不要(可空)。
    • requestId:请求唯一id。方便查询定位某个请求和防止同个请求多次调用。
    • appId:商户id,即我们会给调用方分配一个这样的id,并且将这个id与调用方的信息进行关联,比如“通过appId查询出调用方的加密密钥等”
    • aseKey:是AES对称加密的密钥。用于解密业务请求参数。这里要先用RSA公钥对aseKey进行加密后传输。
    • timestamp:请求时间戳。可以用该字段,对请求的时间合法性进行校验。(如:过滤掉请求时间不在当前时间的正负10分钟范围内的请求)
    • body:请求的业务参数。对请求的业务参数AES加密后再赋值。
    AES工具类
    
    /**
     * @author: Longer
     * @date: 2020/8/23
     * @description: AES工具类
     */
    public class AESUtil {
    
        /**
         * 加密
         * @param content 加密文本
         * @param key 加密密钥,appSecret的前16位
         * @param iv 初始化向量,appSecret的后16位
         * @return
         * @throws Exception
         */
        public static String encrypt(String content, String key, String iv) throws Exception {
            byte[] raw = key.getBytes();
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //"算法/模式/补码方式"
            IvParameterSpec ivParam = new IvParameterSpec(iv.getBytes()); //使用CBC模式,需要一个向量iv,可增加加密算法的强度
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParam);
            byte[] encrypted = cipher.doFinal(content.getBytes());
    
            return new BASE64Encoder().encode(encrypted);
        }
    
        public static String decrypt(String content, String key, String iv) throws Exception {
            byte[] raw = key.getBytes();
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding "); //"算法/模式/补码方式"
            IvParameterSpec ivParam = new IvParameterSpec(iv.getBytes()); //使用CBC模式,需要一个向量iv,可增加加密算法的强度
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParam);
            byte[] encrypted = new BASE64Decoder().decodeBuffer(content); //先用base64解密
            byte[] original = cipher.doFinal(encrypted);
            return new String(original);
        }
    
        public static void main(String[] args) throws Exception {
            String encrypt = AESUtil.encrypt("你好啊!!!", "1234567890123456", "1234567890123456");
            String decrypt = AESUtil.decrypt(encrypt, "1234567890123456", "1234567890123456");
            System.out.println(decrypt);
        }
    
    
    RSA工具类
    /**
     * @author: Longer
     * @date: 2020/8/23
     * @description: RSA工具类
     */
    public class RSAUtil {
    
        /**
         * 定义加密方式
         */
        private final static String KEY_RSA = "RSA";
        /**
         * 定义签名算法
         */
        private final static String KEY_RSA_SIGNATURE = "MD5withRSA";
        /**
         * 定义公钥算法
         */
        private final static String KEY_RSA_PUBLICKEY = "RSAPublicKey";
        /**
         * 定义私钥算法
         */
        private final static String KEY_RSA_PRIVATEKEY = "RSAPrivateKey";
    
        static {
            Security.addProvider(new BouncyCastleProvider());
        }
    
        /**
         * 初始化密钥
         */
        public static Map<String, Object> init() {
            Map<String, Object> map = null;
            try {
                KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_RSA);
                generator.initialize(2048);
                KeyPair keyPair = generator.generateKeyPair();
                // 公钥
                RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
                // 私钥
                RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
                // 将密钥封装为map
                map = new HashMap<>();
                map.put(KEY_RSA_PUBLICKEY, publicKey);
                map.put(KEY_RSA_PRIVATEKEY, privateKey);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            return map;
        }
    
        /**
         * 公钥加密
         *
         * @param data 待加密数据
         * @param key  公钥
         */
        public static byte[] encryptByPublicKey(String data, String key) {
            byte[] result = null;
            try {
                byte[] bytes = decryptBase64(key);
                // 取得公钥
                X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
                KeyFactory factory = KeyFactory.getInstance(KEY_RSA);
                PublicKey publicKey = factory.generatePublic(keySpec);
                // 对数据加密
                Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding", "BC");
    
                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
                byte[] encode = cipher.doFinal(data.getBytes());
                // 再进行Base64加密
                result = Base64.encode(encode);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
    
        /**
         * 私钥解密
         *
         * @param data 加密数据
         * @param key  私钥
         */
        public static String decryptByPrivateKey(byte[] data, String key) {
            String result = null;
            try {
                // 对私钥解密
                byte[] bytes = decryptBase64(key);
                // 取得私钥
                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
                KeyFactory factory = KeyFactory.getInstance(KEY_RSA);
                PrivateKey privateKey = factory.generatePrivate(keySpec);
                // 对数据解密
                Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding", "BC");
                cipher.init(Cipher.DECRYPT_MODE, privateKey);
                // 先Base64解密
                byte[] decoded = Base64.decode(data);
                result = new String(cipher.doFinal(decoded));
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
    
    
        /**
         * 获取公钥
         */
        public static String getPublicKey(Map<String, Object> map) {
            String str = "";
            try {
                Key key = (Key) map.get(KEY_RSA_PUBLICKEY);
                str = encryptBase64(key.getEncoded());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return str;
        }
    
        /**
         * 获取私钥
         */
        public static String getPrivateKey(Map<String, Object> map) {
            String str = "";
            try {
                Key key = (Key) map.get(KEY_RSA_PRIVATEKEY);
                str = encryptBase64(key.getEncoded());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return str;
        }
    
        /**
         * 用私钥对信息生成数字签名
         *
         * @param data       加密数据
         * @param privateKey 私钥
         */
        public static String sign(byte[] data, String privateKey) {
            String str = "";
            try {
                // 解密由base64编码的私钥
                byte[] bytes = decryptBase64(privateKey);
                // 构造PKCS8EncodedKeySpec对象
                PKCS8EncodedKeySpec pkcs = new PKCS8EncodedKeySpec(bytes);
                // 指定的加密算法
                KeyFactory factory = KeyFactory.getInstance(KEY_RSA);
                // 取私钥对象
                PrivateKey key = factory.generatePrivate(pkcs);
                // 用私钥对信息生成数字签名
                Signature signature = Signature.getInstance(KEY_RSA_SIGNATURE);
                signature.initSign(key);
                signature.update(data);
                str = encryptBase64(signature.sign());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return str;
        }
    
        /**
         * 校验数字签名
         *
         * @param data      加密数据
         * @param publicKey 公钥
         * @param sign      数字签名
         * @return 校验成功返回true,失败返回false
         */
        public static boolean verify(byte[] data, String publicKey, String sign) {
            boolean flag = false;
            try {
                // 解密由base64编码的公钥
                byte[] bytes = decryptBase64(publicKey);
                // 构造X509EncodedKeySpec对象
                X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
                // 指定的加密算法
                KeyFactory factory = KeyFactory.getInstance(KEY_RSA);
                // 取公钥对象
                PublicKey key = factory.generatePublic(keySpec);
                // 用公钥验证数字签名
                Signature signature = Signature.getInstance(KEY_RSA_SIGNATURE);
                signature.initVerify(key);
                signature.update(data);
                flag = signature.verify(decryptBase64(sign));
            } catch (Exception e) {
                e.printStackTrace();
            }
            return flag;
        }
    
    
        /**
         * BASE64 解密
         *
         * @param key 需要解密的字符串
         * @return 字节数组
         */
        public static byte[] decryptBase64(String key) throws Exception {
            return Base64.decode(key);
        }
    
        /**
         * BASE64 加密
         *
         * @param key 需要加密的字节数组
         * @return 字符串
         */
        public static String encryptBase64(byte[] key) throws Exception {
            return new String(Base64.encode(key));
        }
    
        /**
         * 按照红黑树(Red-Black tree)的 NavigableMap 实现
         * 按照字母大小排序
         */
        public static Map<String, Object> sort(Map<String, Object> map) {
            if (map == null) {
                return null;
            }
            Map<String, Object> result = new TreeMap<>((Comparator<String>) (o1, o2) -> {
                return o1.compareTo(o2);
            });
            result.putAll(map);
            return result;
        }
    
        /**
         * 组合参数
         *
         * @param map
         * @return 如:key1Value1Key2Value2....
         */
        public static String groupStringParam(Map<String, Object> map) {
            if (map == null) {
                return null;
            }
            StringBuffer sb = new StringBuffer();
            for (Map.Entry<String, Object> item : map.entrySet()) {
                if (item.getValue() != null) {
                    sb.append(item.getKey());
                    if (item.getValue() instanceof List) {
                        sb.append(JSON.toJSONString(item.getValue()));
                    } else {
                        sb.append(item.getValue());
                    }
                }
            }
            return sb.toString();
        }
    
        /**
         * bean转map
         * @param obj
         * @return
         */
        public static Map<String, Object> bean2Map(Object obj) {
            if (obj == null) {
                return null;
            }
            Map<String, Object> map = new HashMap<>();
            try {
                BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
                PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
                for (PropertyDescriptor property : propertyDescriptors) {
                    String key = property.getName();
                    // 过滤class属性
                    if (!key.equals("class")) {
                        // 得到property对应的getter方法
                        Method getter = property.getReadMethod();
                        Object value = getter.invoke(obj);
                        if (StringUtils.isEmpty(value)) {
                            continue;
                        }
                        map.put(key, value);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return map;
        }
        
        /**
         * 按照红黑树(Red-Black tree)的 NavigableMap 实现
         * 按照字母大小排序
         */
        public static Map<String, Object> sort(Map<String, Object> map) {
            if (map == null) {
                return null;
            }
            Map<String, Object> result = new TreeMap<>((Comparator<String>) (o1, o2) -> {
                return o1.compareTo(o2);
            });
            result.putAll(map);
            return result;
        }
    
        /**
         * 组合参数
         *
         * @param map
         * @return 如:key1Value1Key2Value2....
         */
        public static String groupStringParam(Map<String, Object> map) {
            if (map == null) {
                return null;
            }
            StringBuffer sb = new StringBuffer();
            for (Map.Entry<String, Object> item : map.entrySet()) {
                if (item.getValue() != null) {
                    sb.append(item.getKey());
                    if (item.getValue() instanceof List) {
                        sb.append(JSON.toJSONString(item.getValue()));
                    } else {
                        sb.append(item.getValue());
                    }
                }
            }
            return sb.toString();
        }
    
        /**
         * bean转map
         * @param obj
         * @return
         */
        public static Map<String, Object> bean2Map(Object obj) {
            if (obj == null) {
                return null;
            }
            Map<String, Object> map = new HashMap<>();
            try {
                BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
                PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
                for (PropertyDescriptor property : propertyDescriptors) {
                    String key = property.getName();
                    // 过滤class属性
                    if (!key.equals("class")) {
                        // 得到property对应的getter方法
                        Method getter = property.getReadMethod();
                        Object value = getter.invoke(obj);
                        if (StringUtils.isEmpty(value)) {
                            continue;
                        }
                        map.put(key, value);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return map;
        }
    
    
        public static void main(String[] args) throws Exception {
            System.out.println("公钥加密======私钥解密");
            String str = "Longer程序员";
            byte[] enStr = RSAUtil.encryptByPublicKey(str, publicKey);
            String miyaoStr = HexUtils.bytesToHexString(enStr);
            byte[] bytes = HexUtils.hexStringToBytes(miyaoStr);
            String decStr = RSAUtil.decryptByPrivateKey(bytes, privateKey);
            System.out.println("加密前:" + str + "\n\r解密后:" + decStr);
    
            System.out.println("\n\r");
            System.out.println("私钥签名======公钥验证");
            String sign = RSAUtil.sign(str.getBytes(), privateKey);
            System.out.println("签名:\n\r" + sign);
            boolean flag = RSAUtil.verify(str.getBytes(), publicKey, sign);
            System.out.println("验签结果:\n\r" + flag);
        }
    
    
    jackson工具类
    
    import com.fasterxml.jackson.databind.JavaType;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import lombok.extern.slf4j.Slf4j;
    import org.codehaus.jackson.type.TypeReference;
    import org.springframework.util.StringUtils;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    
    /**
     * @author: Longer
     * @date: 2020/8/23
     * @description: Jackson转换工具类
     */
    @Slf4j
    public class JacksonUtil {
        private static ObjectMapper objectMapper = new ObjectMapper();
    
        /**
         * 对象转换成json
         *
         * @param obj
         * @param <T>
         * @return
         */
        public static <T> String beanToJson(T obj) {
            if (obj == null) {
                return null;
            }
            try {
                return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
            } catch (Exception e) {
                log.error("beanToJson error", e);
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 将JSON字符串根据指定的Class反序列化成Java对象。
         *
         * @param json      JSON字符串
         * @param pojoClass Java对象Class
         * @return 反序列化生成的Java对象
         * @throws Exception 如果反序列化过程中发生错误,将抛出异常
         */
        public static Object decode(String json, Class<?> pojoClass)
                throws Exception {
            try {
                return objectMapper.readValue(json, pojoClass);
            } catch (Exception e) {
                throw e;
            }
        }
    
        /**
         * 将JSON字符串根据指定的Class反序列化成Java对象。
         *
         * @param json      JSON字符串
         * @param reference 类型引用
         * @return 反序列化生成的Java对象
         * @throws Exception 如果反序列化过程中发生错误,将抛出异常
         */
        public static Object decode(String json, TypeReference<?> reference) throws Exception {
            try {
                return objectMapper.readValue(json, reference.getClass());
            } catch (Exception e) {
                throw e;
            }
        }
    
        /**
         * 将Java对象序列化成JSON字符串。
         *
         * @param obj 待序列化生成JSON字符串的Java对象
         * @return JSON字符串
         * @throws Exception 如果序列化过程中发生错误,将抛出异常
         */
        public static String encode(Object obj) throws Exception {
            try {
                return objectMapper.writeValueAsString(obj);
            } catch (Exception e) {
                throw e;
            }
        }
    
        /**
         * 对象转换成格式化的json
         *
         * @param obj
         * @param <T>
         * @return
         */
        public static <T> String beanToJsonPretty(T obj) {
            if (obj == null) {
                return null;
            }
            try {
                return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
            } catch (Exception e) {
                log.error("beanToJsonPretty error", e);
                e.printStackTrace();
                return null;
            }
        }
    
    
        /**
         * 将json转换成对象Class
         *
         * @param str
         * @param clazz
         * @param <T>
         * @return
         */
        public static <T> T jsonToBean(String str, Class<T> clazz) {
            if (StringUtils.isEmpty(str) || clazz == null) {
                return null;
            }
            try {
                return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz);
            } catch (Exception e) {
                log.error("jsonToBean error", e);
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 将json转换为对象集合
         *
         * @param str
         * @param clazz
         * @param <T>
         * @return
         */
        public static <T> List<T> jsonToBeanList(String str, Class<T> clazz) {
            if (StringUtils.isEmpty(str) || clazz == null) {
                return null;
            }
            JavaType javaType = getCollectionType(ArrayList.class, clazz);
            try {
                return objectMapper.readValue(str, javaType);
            } catch (IOException e) {
                log.error("jsonToBeanList error", e);
                e.printStackTrace();
                return null;
            }
        }
        public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
            return objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
        }
    }
    
    
    加密解密验签名流程演示
    /**
     * @author: Longer
     * @date: 2020/8/23
     * @description: 测试
     */
    public class RequestTest {
        public static void main(String[] args) {
            /****先给调用方分配一组RSA密钥和一个appId****/
            //初始化RSA密钥
            Map<String, Object> init = RSAUtil.init();
            //私钥
            String privateKey = RSAUtil.getPrivateKey(init);
            //公钥
            String publicKey = RSAUtil.getPublicKey(init);
            //appId,32位的uuid
            String appId = getUUID32();
            /****先给调用方分配一组RSA密钥和一个appId****/
    
            /*****调用方(请求方)*****/
            //业务参数
            Map<String,Object>  businessParams = new HashMap<>();
            businessParams.put("name","Longer");
            businessParams.put("job","程序猿");
            businessParams.put("hobby","打篮球");
    
            JsonRequest jsonRequest = new JsonRequest();
            jsonRequest.setRequestId(getUUID32());
            jsonRequest.setAppId(appId);
            jsonRequest.setTimestamp(System.currentTimeMillis());
            //使用appId的前16位作为AES密钥,并对密钥进行rsa公钥加密
            String aseKey = appId.substring(0, 16);
            byte[] enStr = RSAUtil.encryptByPublicKey(aseKey, publicKey);
            String aseKeyStr = HexUtils.bytesToHexString(enStr);
            jsonRequest.setAseKey(aseKeyStr);
            //请求的业务参数进行加密
            String body = "";
            try {
                body = AESUtil.encrypt(JacksonUtil.beanToJson(businessParams), aseKey, appId.substring(16));
            } catch (Exception e) {
                throw new RuntimeException("报文加密异常", e);
            }
            jsonRequest.setBody(body);
            //签名
            Map<String, Object> paramMap = RSAUtil.bean2Map(jsonRequest);
            paramMap.remove("sign");
            // 参数排序
            Map<String, Object> sortedMap = RSAUtil.sort(paramMap);
            // 拼接参数:key1Value1key2Value2
            String urlParams = RSAUtil.groupStringParam(sortedMap);
            //私钥签名
            String sign = RSAUtil.sign(HexUtils.hexStringToBytes(urlParams), privateKey);
            jsonRequest.setSign(sign);
    
            /*****调用方(请求方)*****/
    
            /*****接收方(自己的系统)*****/
            //参数判空(略)
            //appId校验(略)
            //本条请求的合法性校验《唯一不重复请求;时间合理》(略)
            //验签
            Map<String, Object> paramMap2 = RSAUtil.bean2Map(jsonRequest);
            paramMap2.remove("sign");
            //参数排序
            Map<String, Object> sortedMap2 = RSAUtil.sort(paramMap2);
            //拼接参数:key1Value1key2Value2
            String urlParams2 = RSAUtil.groupStringParam(sortedMap2);
            //签名验证
            boolean verify = RSAUtil.verify(HexUtils.hexStringToBytes(urlParams2), publicKey, jsonRequest.getSign());
            if (!verify) {
                throw new RuntimeException("签名验证失败");
            }
            //私钥解密,获取aseKey
            String aseKey2 = RSAUtil.decryptByPrivateKey(HexUtils.hexStringToBytes(jsonRequest.getAseKey()), privateKey);
            if (!StringUtils.isEmpty(jsonRequest.getBody())) {
                // 解密请求报文
                String requestBody = "";
                try {
                    requestBody = AESUtil.decrypt(jsonRequest.getBody(), aseKey, jsonRequest.getAppId().substring(16));
                } catch (Exception e) {
                    throw new RuntimeException("请求参数解密异常");
                }
                System.out.println("业务参数解密结果:"+requestBody);
            }
            /*****接收方(自己的系统)*****/
        }
    
        public static String getUUID32() {
            String uuid = UUID.randomUUID().toString();
            uuid = uuid.replace("-", "");
            return uuid;
        }
    }
    
    

    执行结果:

    业务参数解密结果:{"name":"Longer","job":"程序猿","hobby":"打篮球"}
    

    到此,调用方要做的和接收方做的其实都已经清楚了。

    调用方:

    • 1.业务参数进行AES对称加密
    • 2.AES密钥进行RSA非对称加密
    • 3.使用RSA生成签名

    接收方:

    • 验证签名
    • AES密钥解密
    • 业务参数解密

    请求参数的统一处理

    上面讲到,我们接受的请求对象是JsonRequst对象,里面除了body成员变量是跟业务相关,其他成员变量(sericeId,appId等)都是与业务不相关的。那么,如果我们在controller层用JsonRequest对象去接收请求参数的话,其实是不那么规范的。
    那么我们能不能对请求参数进行统一处理,使得传到controller层的参数只是跟业务相关的参数,并且在controller层也无需关注加密解密和验签的东西。

    实现方法:
    • 使用过滤器,拦截请求,并对请求参数进行统一处理(加密解密,验签等)
    • 自定义request对象(新增类继承HttpServletRequestWrapper类),对请求参数进行过滤处理,使得controller只接受业务参数。

    问题:为什么需要自定义request对象?
    因为获取post请求传递的json对象,需要用request对象流取获取,而一旦我们调用了request.getInputStream()方法后,流将会自动关闭,那么到了我们的controller层就不能再获取到请求参数了。

    自定义request对象
    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;
    
    /**
     * @author Longer
     * @description 获取请求参数并处理
     * @date 2020/8/23
     */
    public class RequestWrapper extends HttpServletRequestWrapper {
        private byte[] body;
    
        public RequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            String sessionStream = getBodyString(request);
            body = sessionStream.getBytes(Charset.forName("UTF-8"));
        }
    
        public String getBodyString() {
            return new String(body, Charset.forName("UTF-8"));
        }
    
        public void setBodyString(byte[] bodyByte){
            body = bodyByte;
        }
    
        /**
         * 获取请求Body
         *
         * @param request
         * @return
         */
        public String getBodyString(final ServletRequest request) {
            StringBuilder sb = new StringBuilder();
            InputStream inputStream = null;
            BufferedReader reader = null;
            try {
                inputStream = cloneInputStream(request.getInputStream());
                reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
                String line = "";
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return sb.toString();
        }
    
        /**
         * Description: 复制输入流</br>
         *
         * @param inputStream
         * @return</br>
         */
        public InputStream cloneInputStream(ServletInputStream inputStream) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            try {
                while ((len = inputStream.read(buffer)) > -1) {
                    byteArrayOutputStream.write(buffer, 0, len);
                }
                byteArrayOutputStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
            InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            return byteArrayInputStream;
        }
    
        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }
    
        @Override
        public ServletInputStream getInputStream() throws IOException {
    
            final ByteArrayInputStream bais = new ByteArrayInputStream(body);
            return new ServletInputStream() {
    
                @Override
                public int read() throws IOException {
                    return bais.read();
                }
    
                @Override
                public boolean isFinished() {
                    return false;
                }
    
                @Override
                public boolean isReady() {
                    return false;
                }
    
                @Override
                public void setReadListener(ReadListener readListener) {
    
                }
            };
        }
    }
    
    自定义过滤器
    /**
     * @author Longer
     * @description 获取请求参数并处理。签名校验,报文解密,参数过滤。
     * @date 2020/8/23
     */
    @Slf4j
    public class OutRequestFilter extends OncePerRequestFilter {
    
    
        @SneakyThrows
        @Override
        protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException {
            RedisUtil redisUtil = SpringUtils.getBean(RedisUtil.class);
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            String requestURL = request.getRequestURI();
            log.info("请求路径:" + requestURL);
            String method = request.getMethod();
            if (!"POST".equals(method)) {
                throw new RuntimeException("暂不支持" + method + "请求方式");
            }
            //获取请求参数
            RequestWrapper requestWrapper = new RequestWrapper(request);
            String bodyString = requestWrapper.getBodyString();
            if (StringUtils.isEmpty(bodyString)) {
                throw new RuntimeException("请求体不能为空");
            }
            log.info("请求参数:" + bodyString);
            JsonRequest jsonRequest = JacksonUtil.jsonToBean(bodyString, JsonRequest.class);
            //step0 参数合法性校验(非空判断等)
            parameterValidate(jsonRequest);
            //step1 判断请求合法性。1.不允许重复请求(通过请求唯一id判断)2.不允许请求时间与当前时间差距过大(正负10分钟)
            long currentTime = System.currentTimeMillis();
            long subTime = currentTime - jsonRequest.getTimestamp();
            long tenMinuteMs = 10 * 60 * 1000;
            if (subTime < -tenMinuteMs || subTime > tenMinuteMs) {
                throw new RuntimeException("请求异常,请求时间异常");
            }
            String requestUUIDKey = MessageFormat.format(RedisConstant.REQUEST_UUID, jsonRequest.getRequestId());
            Object requestFlag = redisUtil.get(requestUUIDKey);
            if (!StringUtils.isEmpty(requestFlag)) {
                throw new RuntimeException("请求异常,重复的请求");
            }
            redisUtil.set(requestUUIDKey, JacksonUtil.beanToJson(jsonRequest), 15 * 60);
            //step2 参数解密,签名校验,参数过滤和传递
            Map<String, Object> paramMap = RSAUtil.bean2Map(jsonRequest);
            paramMap.remove("sign");
            //根据appkey获取rsa密钥
            String appIdKey = MessageFormat.format(RedisConstant.REQUEST_APPID, jsonRequest.getAppId());
            Object ob = redisUtil.get(appIdKey);
            if (StringUtils.isEmpty(ob)) {
                throw new RuntimeException("找不到指定的appid");
            }
            String jsonString = (String) ob;
            JSONObject jsonObject = JSONObject.parseObject(jsonString);
            //rsa公钥
            String publicKey = jsonObject.getString("publicKey");
            //rsa私钥
            String privateKey = jsonObject.getString("privateKey");
            //参数排序
            Map<String, Object> sortedMap = RSAUtil.sort(paramMap);
            //拼接参数:key1Value1key2Value2
            String urlParams = RSAUtil.groupStringParam(sortedMap);
            //签名验证
            boolean verify = RSAUtil.verify(HexUtils.hexStringToBytes(urlParams), publicKey, jsonRequest.getSign());
            if (!verify) {
                throw new RuntimeException("签名验证失败");
            }
            //私钥解密,获取aseKey
            String aseKey = RSAUtil.decryptByPrivateKey(HexUtils.hexStringToBytes(jsonRequest.getAseKey()), privateKey);
            if (!StringUtils.isEmpty(jsonRequest.getBody())) {
                // 解密请求报文
                String body = "";
                try {
                    body = AESUtil.decrypt(jsonRequest.getBody(), aseKey, jsonRequest.getAppId().substring(16));
                } catch (Exception e) {
                    log.error("请求参数解密异常:",e);
                    throw new RuntimeException("请求参数解密异常");
                }
                //报文传递至controller层
                requestWrapper.setBodyString(body.getBytes(Charset.forName("UTF-8")));
            }
            //将request传递下去
            filterChain.doFilter(requestWrapper, servletResponse);
        }
    
        private void parameterValidate(JsonRequest jsonRequest) {
            if (StringUtils.isEmpty(jsonRequest.getAppId())) {
                throw new RuntimeException("参数异常,appId不能为空");
            }
            if (StringUtils.isEmpty(jsonRequest.getAseKey())) {
                throw new RuntimeException("参数异常,aseKey不能为空");
            }
            if (StringUtils.isEmpty(jsonRequest.getRequestId())) {
                throw new RuntimeException("参数异常,requestId不能为空");
            }
            if (StringUtils.isEmpty(jsonRequest.getSign())) {
                throw new RuntimeException("参数异常,sign不能为空");
            }
            if (jsonRequest.getTimestamp() == 0l) {
                throw new RuntimeException("参数异常,timestamp不能为空");
            }
        }
    
    }
    

    完整流程演示

    调用方

    HttpClientUtils类
    /**
     * @author: Longer
     * @description:
     */
    public class HttpClientUtils {
    
        private static Logger logger = Logger.getLogger(HttpClientUtils.class.getName());
    
        public static String doPostJson(String url, String json) {
            // 创建Httpclient对象
            CloseableHttpClient httpClient = HttpClients.createDefault();
            CloseableHttpResponse response = null;
            String resultString = "";
            try {
                // 创建Http Post请求
                HttpPost httpPost = new HttpPost(url);
                // 创建请求内容
                StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
                httpPost.setEntity(entity);
                // 执行http请求
                response = httpClient.execute(httpPost);
                resultString = EntityUtils.toString(response.getEntity(), "utf-8");
            } catch (Exception e) {
                resultString = e.getMessage();
                logger.info("http访问失败:" + e);
    
            } finally {
                try {
                    response.close();
                } catch (IOException e) {
                    logger.info("response关闭失败:" + e);
                }
            }
    
            return resultString;
        }
    
        /**
         * post请求,签名和报文加密
         *
         * @param url        请求地址
         * @param json       请求json参数
         * @param appId      商户id
         * @param publicKey  rsa公钥
         * @param privateKey rsa私钥
         * @return
         */
        public static String doPostJsonForSign(String url, String json, String appId, String publicKey, String privateKey) {
            String aseKey = appId.substring(0, 16);
            JsonRequest jsonRequest = new JsonRequest();
            jsonRequest.setRequestId(getUUID32());
            jsonRequest.setAppId(appId);
            jsonRequest.setTimestamp(System.currentTimeMillis());
            //aseKey 加密
            logger.info("开始aseKey加密....");
            byte[] enStr = RSAUtil.encryptByPublicKey(aseKey, publicKey);
            String aseKeyStr = HexUtils.bytesToHexString(enStr);
            jsonRequest.setAseKey(aseKeyStr);
            //请求参数进行加密
            String body = "";
            try {
                logger.info("开始请求参数加密....");
                body = AESUtil.encrypt(json, aseKey, appId.substring(16));
            } catch (Exception e) {
                logger.info("报文加密异常:" + e);
                throw new UncheckedException("报文加密异常", e);
            }
            jsonRequest.setBody(body);
    
            Map<String, Object> paramMap = RSAUtil.bean2Map(jsonRequest);
            paramMap.remove("sign");
            // 参数排序
            Map<String, Object> sortedMap = RSAUtil.sort(paramMap);
            // 拼接参数:key1Value1key2Value2
            String urlParams = RSAUtil.groupStringParam(sortedMap);
            //私钥签名
            logger.info("开始参数签名....");
            String sign = RSAUtil.sign(HexUtils.hexStringToBytes(urlParams), privateKey);
            jsonRequest.setSign(sign);
            String requestParams = JacksonUtil.beanToJson(jsonRequest);
            logger.info("发起请求....");
            String result = doPostJson(url, requestParams);
            return result;
        }
    
        public static String getUUID32() {
            String uuid = UUID.randomUUID().toString();
            uuid = uuid.replace("-", "");
            return uuid;
        }
    
    }
    
    
    需要传递的业务参数对象
    @Data
    public class RequestDto {
        private String name;
        private int age;
        private String hobby;
    }
    
    发送请求
     public static void main(String[] args) {
            //请求地址
            String url = "http://127.0.0.1:8888/test";
            RequestDto requestDto = new RequestDto();
            requestDto.setAge(100);
            requestDto.setName("Longer");
            requestDto.setHobby("ball");
            String json = JacksonUtil.beanToJson(requestDto);
            //appId
            String appId = "";
            //rsa公钥
            String publicKey = "";
            //rsa私钥
            String privateKey = "";
            HttpClientUtils.doPostJsonForSign(url, json, appId, publicKey, privateKey)
        }
    

    接收方

    controller
    @Slf4j
    @RestController
    public class TestController {
    
        @RequestMapping("test")
        public String test(RequestDto requestDto){
            log.info("接收到的请求参数为:"+ JacksonUtil.beanToJson(requestDto));
            return "a";
        }
    }
    

    因为我们对参数进行了统一处理,所以我们的controller接收参数的对象是RequestDto对象,而不是JsonRequest对象

    相关文章

      网友评论

        本文标题:RSA+AES实现接口验签和参数加密

        本文链接:https://www.haomeiwen.com/subject/ffdesktx.html