您当前的位置:首页 > 电脑百科 > 程序开发 > 算法

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

时间:2021-07-04 14:10:15  来源:简书  作者:Java架构学习指南

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 + "nr解密后:" + decStr);

        System.out.println("nr");
        System.out.println("私钥签名======公钥验证");
        String sign = RSAUtil.sign(str.getBytes(), privateKey);
        System.out.println("签名:nr" + sign);
        boolean flag = RSAUtil.verify(str.getBytes(), publicKey, sign);
        System.out.println("验签结果:nr" + 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对象

原文链接:
https://www.jianshu.com/p/9061da5e25d1



Tags:接口   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
首先我们要弄懂什么是“雷电”接口。其实“雷电”本质上和咱们常见的HDMI,DP等一样是一种传输协议的简称,全程英文名叫做Thunderbolt。请注意与其说他是一个接口倒不如说他是...【详细内容】
2021-12-27  Tags: 接口  点击:(2)  评论:(0)  加入收藏
Hi,大家好。我们在接口自动化测试项目中,有时候需要一些加密。今天给大伙介绍Python实现各种 加密 ,接口加解密再也不愁。目录一、项目加解密需求分析六、Python加密库PyCrypto...【详细内容】
2021-12-21  Tags: 接口  点击:(7)  评论:(0)  加入收藏
最近在逛知乎的时候发现一个有趣的问题:《公司规定所有接口都用 post 请求,这是为什么?》原问题:zhihu.com/question/336797348看到这个问题的时候其实我也挺有感触的,因为我也...【详细内容】
2021-12-08  Tags: 接口  点击:(24)  评论:(0)  加入收藏
requests一、简介使用 python 做自动化接口测试需要用代码发送 http 请求。requests 是 Python 语言里网络请求库中最好用的,没有之一。requests 库有完善详尽的中文版官方...【详细内容】
2021-12-01  Tags: 接口  点击:(14)  评论:(0)  加入收藏
public class LambdaDemo { public static void main(String[] args) { /** * 用来判定true或者false boolean test(T t); */ Predicat...【详细内容】
2021-10-18  Tags: 接口  点击:(56)  评论:(0)  加入收藏
一、幂等性概念在数学里,幂等有两种主要的定义。1、在某二元运算下,幂等元素是指被自己重复运算(或对于函数是为复合)的结果等于它自己的元素。例如,乘法下唯一两个幂等实数为0和...【详细内容】
2021-10-09  Tags: 接口  点击:(43)  评论:(0)  加入收藏
Guava提供的RateLimiter可以限制物理或逻辑资源的被访问速率,咋一听有点像java并发包下的Samephore,但是又不相同,RateLimiter控制的是速率,Samephore控制的是并发量。RateLimit...【详细内容】
2021-09-17  Tags: 接口  点击:(72)  评论:(0)  加入收藏
相对于HDD(机械硬盘)来说,SSD(固态硬盘)拥有速度快、无噪音、轻便、防震抗摔等优点,而这也让不少人在为旧电脑升级或扩展存储空间时会优先考虑SSD。但在选购的时候,我们会发现SSD有...【详细内容】
2021-09-14  Tags: 接口  点击:(77)  评论:(0)  加入收藏
前言上次有写过一篇《20张图深度详解MAC地址表、ARP表、路由表》的文章,里面有提到了MAC地址表。那么什么是MAC地址表?MAC地址表有什么作用?MAC地址表里面包含了哪些要素?今天...【详细内容】
2021-09-09  Tags: 接口  点击:(76)  评论:(0)  加入收藏
前言前后端分离开发模式中,api文档是最好的沟通方式。今天就来说一说如何整合Swagger生成一套漂亮、美观、实用的接口文档。 源码传送门: https://gitee.com/huoqstudy/xiliu-...【详细内容】
2021-09-08  Tags: 接口  点击:(65)  评论:(0)  加入收藏
▌简易百科推荐
前言Kafka 中有很多延时操作,比如对于耗时的网络请求(比如 Produce 是等待 ISR 副本复制成功)会被封装成 DelayOperation 进行延迟处理操作,防止阻塞 Kafka请求处理线程。Kafka...【详细内容】
2021-12-27  Java技术那些事    Tags:时间轮   点击:(1)  评论:(0)  加入收藏
博雯 发自 凹非寺量子位 报道 | 公众号 QbitAI在炼丹过程中,为了减少训练所需资源,MLer有时会将大型复杂的大模型“蒸馏”为较小的模型,同时还要保证与压缩前相当的结果。这就...【详细内容】
2021-12-24  量子位    Tags:蒸馏法   点击:(9)  评论:(0)  加入收藏
分稀疏重建和稠密重建两类:稀疏重建:使用RGB相机SLAMOrb-slam,Orb-slam2,orb-slam3:工程地址在: http://webdiis.unizar.es/~raulmur/orbslam/ DSO(Direct Sparse Odometry)因为...【详细内容】
2021-12-23  老师明明可以靠颜值    Tags:算法   点击:(7)  评论:(0)  加入收藏
1. 基本概念希尔排序又叫递减增量排序算法,它是在直接插入排序算法的基础上进行改进而来的,综合来说它的效率肯定是要高于直接插入排序算法的;希尔排序是一种不稳定的排序算法...【详细内容】
2021-12-22  青石野草    Tags:希尔排序   点击:(6)  评论:(0)  加入收藏
ROP是一种技巧,我们对execve函数进行拼凑来进行system /bin/sh。栈迁移的特征是溢出0x10个字符,在本次getshell中,还碰到了如何利用printf函数来进行canary的泄露。ROP+栈迁移...【详细内容】
2021-12-15  星云博创    Tags:栈迁移   点击:(19)  评论:(0)  加入收藏
一、什么是冒泡排序1.1、文字描述冒泡排序是一种简单的排序算法。它重复地走访要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地...【详细内容】
2021-12-15    晓掌柜丶韶华  Tags:排序算法   点击:(16)  评论:(0)  加入收藏
在了解golang的map之前,我们需要了解哈希这个概念。哈希表,又称散列表(Hash table),是根据键(key)而直接访问在内存储存位置的数据结构。也就是说,它通过计算出一个键值的函数,将...【详细内容】
2021-12-07  一棵梧桐木    Tags:哈希表   点击:(13)  评论:(0)  加入收藏
前面文章在谈论分布式唯一ID生成的时候,有提到雪花算法,这一次,我们详细点讲解,只讲它。SnowFlake算法据国家大气研究中心的查尔斯&middot;奈特称,一般的雪花大约由10^19个水分子...【详细内容】
2021-11-17  小心程序猿QAQ    Tags:雪花算法   点击:(24)  评论:(0)  加入收藏
导读:在大数据时代,对复杂数据结构中的各数据项进行有效的排序和查找的能力非常重要,因为很多现代算法都需要用到它。在为数据恰当选择排序和查找策略时,需要根据数据的规模和类型进行判断。尽管不同策略最终得到的结果完...【详细内容】
2021-11-04  华章科技    Tags:排序算法   点击:(37)  评论:(0)  加入收藏
这是我在网上找的资源的一个总结,会先给出一个我看了觉得还行的关于算法的讲解,再配上实现的代码: Original author: Bill_Hoo Original Address: http://blog.sina.com.cn/s/bl...【详细内容】
2021-11-04  有AI野心的电工和码农    Tags: KMP算法   点击:(36)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条