RSA asymmetric encryption

RSA is a common asymmetric encryption algorithm. Encryption and encryption use different keys. It is commonly used in encryption scenarios that require high security, such as interface signature check and interface data encryption and decryption. Compared with asymmetric encryption algorithm, its security is higher, but the encryption performance is lower, not suitable for high concurrency scenarios, generally only a small amount of data encryption.

AES symmetric encryption

AES is one of the most common symmetric encryption algorithms (wechat applet encryption is transmitted using this encryption algorithm), encryption and decryption using the same key. Its encryption performance is good, encryption and decryption speed is very fast, low memory requirements, suitable for the occasion of frequently sending data.

RSA+AES realizes the encryption and decryption of interface check and request parameters

Background: As a program monkey, we often need to develop some interfaces for the third party to call on our own developed system, so at this time, the security requirements of our interfaces are relatively high, especially when the need to transmit more private information, its security requirements are higher.

Implementation approach

Caller:

  • The AES symmetric encryption algorithm is used to encrypt the service request parameters before transmission
  • The RSA asymmetric encryption algorithm is used to encrypt AES keys
  • The request parameters are signed using the RSA private key

Receiver:

  • After obtaining the request parameters, check the parameters and decrypt the service parameters

Question: Why is the AES key encrypted with RSA public key transmitted?

AES is a symmetric encryption algorithm. The encryption and decryption keys are the same. To prevent malicious users from obtaining the key and decrypting our service request parameters, you need to encrypt the AES key asymmetrically before transmitting it.

Code implementation

<! --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> The < version > 1.56 < / version > < / dependency > <! - Jackson dependence - > < the dependency > < groupId > com. Fasterxml. Jackson. Core < / groupId > < artifactId > Jackson - databind < / artifactId > The < version > 2.9.8 < / version > < / dependency > <! --> <dependency> <groupId>org.codehaus. Jackson </groupId> <artifactId>jackson-core-asl</artifactId> The < version > 1.8.3 < / version > < / dependency >Copy the code
Entity objects requested and received
@data public class JsonRequest {// Interface ID can be empty private String serviceId; // Request unique ID non-empty private String requestId; // Merchant ID non-empty private String appId; // Parameter signature is non-empty private String sign; // Symmetric encryption key non-empty private String aseKey; Private long timestamp; // Request business parameters (passed after AES encryption) can be null private String body; }Copy the code
  • ServiceId: indicates the id of a service (interface). Interface design is divided into two types. One is that all invocation policies invoke the same interface address for similar services, and the internal system determines which service method to invoke based on the serviceId. The serviceId field can be omitted if the interface address is different for different callers. This chapter uses the second method, so serviceId can be left blank.
  • RequestId: indicates the unique ID of the request. It facilitates queries to locate a request and prevents multiple calls to the same request.
  • AppId: merchant ID, that is, we will assign such an ID to the caller and associate this ID with the caller’s information, such as “query the caller’s encryption key through appId, etc.”
  • AseKey: is the key of AES symmetric encryption. Used to decrypt business request parameters. In this case, use the RSA public key to encrypt aseKey and then transfer it.
  • Timestamp: request timestamp. You can use this field to verify the time validity of a request. (e.g., filter out requests whose request time is not within plus or minus 10 minutes of the current time)
  • Body: Business parameter of the request. The requested service parameters are encrypted using AES and then assigned.
AES tools
/** * @author: Longer * @date: 2020/8/23 * @description: AES utility class */ public class AESUtil {/** * encryption * @param content Encrypted text * @param key encryption key, first 16 bits of appSecret * @param iv initialization vector, AppSecret the last 16 bits * @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"); //" Algorithm/mode/Complement mode "IvParameterSpec ivParam = new IvParameterSpec(iv.getBytes()); // To use CBC mode, a vector iv is required to increase the strength of the encryption algorithm 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 "); //" Algorithm/mode/Complement mode "IvParameterSpec ivParam = new IvParameterSpec(iv.getBytes()); Cipher.init (cipher.decrypt_mode, skeySpec, ivParam); byte[] encrypted = new BASE64Decoder().decodeBuffer(content); Byte [] Original = cipher.dofinal (encrypted); return new String(original); } public static void main(String[] args) throws Exception {String encrypt = aesutil. encrypt(" Hello!! , "1234567890123456", "1234567890123456"); String decrypt = AESUtil.decrypt(encrypt, "1234567890123456", "1234567890123456"); System.out.println(decrypt); }Copy the code
RSA tools
/**
 * @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);
    }

Copy the code
Jackson tools
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: @slf4j public class JacksonUtil {private static ObjectMapper ObjectMapper = new ObjectMapper(); Public static <T> String beanToJson(T obj) {if (obj == null) {public static <T> String beanToJson(T obj) {  return null; } try { return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); } catch (Exception e) { log.error("beanToJson error", e); e.printStackTrace(); return null; }} /** * deserializes the JSON string into a Java object based on the specified Class. * * @param JSON JSON string * @param pojoClass Java object Class * @return Java object generated by deserialization * @throws Exception If an error occurs during deserialization, Public static Object decode(String json, Class<? > pojoClass) throws Exception { try { return objectMapper.readValue(json, pojoClass); } catch (Exception e) { throw e; }} /** * deserializes the JSON string into a Java object based on the specified Class. * * @param JSON JSON string * @Param Reference type reference * @return Deserialization of the generated Java object * @throws Exception If an error occurs during deserialization, Public static Object decode(String json, TypeReference<? > reference) throws Exception { try { return objectMapper.readValue(json, reference.getClass()); } catch (Exception e) { throw e; }} /** * serializes a Java object into a JSON string. * * @param obj Java object to be serialized to generate JSON string * @return JSON string * @throws Exception If an error occurs during serialization, Will throw an Exception * / public static String encode (Object obj) throws the Exception {try {return objectMapper. WriteValueAsString (obj); } catch (Exception e) { throw e; }} public static <T> String beanToJsonPretty(T obj) {if}} public static <T> String beanToJsonPretty(T obj) {if}} 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; 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; @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); }}Copy the code
Encryption and decryption signature check process demonstration
/** * @author: Longer * @date: 2020/8/23 * @description: Public class RequestTest {public static void main(String[] args) {/**** Allocates an RSA key to the caller and an appId****/ / initialises the RSA key Map<String, Object> init = RSAUtil.init(); PrivateKey String privateKey = rsautil. getPrivateKey(init); PublicKey String publicKey = rsautil. getPublicKey(init); // publicKey String publicKey = rsautil. getPublicKey(init); //appId, 32-bit uuid String appId = getUUID32(); /**** Allocate a set of RSA keys and an appId to the caller ****/ /***** the caller (requestor) *****/ // Business parameters Map<String,Object> businessParams = new HashMap<>(); businessParams.put("name","Longer"); BusinessParams. Put ("job"," program ape "); BusinessParams. Put ("hobby"," playing basketball "); JsonRequest jsonRequest = new JsonRequest(); jsonRequest.setRequestId(getUUID32()); jsonRequest.setAppId(appId); jsonRequest.setTimestamp(System.currentTimeMillis()); String aseKey = AppID.subString (0, 16); // Use the first 16 bits of the appId as the AES key and encrypt the key with the RSA public key. 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(" packet encryption Exception ", e); } jsonRequest.setBody(body); // Signature Map<String, Object> paramMap = rsautil. bean2Map(jsonRequest); paramMap.remove("sign"); // Parameter sort Map<String, Object> sortedMap = rsautil.sort (paramMap); Key1Value1key2Value2 String urlParams = rsautil. groupStringParam(sortedMap); key1Value1key2Value2 String urlParams = rsautil. groupStringParam(sortedMap); / / private key signature String sign = RSAUtil. Sign (HexUtils. HexStringToBytes (urlParams), privateKey); jsonRequest.setSign(sign); /***** caller (requestor) *****/ /***** Receiver (own system) *****/ / Parameter null (omitted) //appId verification (omitted) // Validity verification of this request The only non-repeated request; Reasonable time (omitted) // Check Map<String, Object> paramMap2 = RSAUtil.bean2Map(jsonRequest); paramMap2.remove("sign"); Map<String, Object> sortedMap2 = rsautil.sort (paramMap2); Key1Value1key2Value2 String urlParams2 = rsautil. groupStringParam(sortedMap2); / / signature verification Boolean verify = RSAUtil. Verify (HexUtils. HexStringToBytes (urlParams2), publicKey, jsonRequest. GetSign ()); if (! Verify) {throw new RuntimeException(" signature verification failed "); } // Private key decryption, Get 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(" request parameter decryption Exception "); } system.out. println(" business parameter decryption result: "+requestBody); } /***** *****/} public static String getUUID32() {String uuid = uuid.randomuuid ().toString(); uuid = uuid.replace("-", ""); return uuid; }}Copy the code

Execution Result:

Business parameters decryption results: {" name ":" Longer ", "job" : "program ape", "hobby" : "basketball"}Copy the code

At this point, it’s already clear what the caller and the receiver are going to do.

Caller:

  • 1. Perform SYMMETRIC AES encryption for service parameters
  • 2. The AES key performs RSA asymmetric encryption
  • 3. Use RSA to generate signatures

Receiver:

  • Verify the signature
  • AES Key decryption
  • Service parameter decryption

Unified handling of request parameters

As mentioned above, the request object we accept is the JsonRequst object. Except for the body member variable, other member variables (sericeId,appId, etc.) are not relevant to business. So, if we use the JsonRequest object at the Controller layer to receive the request parameters, it will be less formal. So, can we process the request parameters uniformly, so that the parameters transmitted to the Controller layer are only business-related parameters, and there is no need to pay attention to encryption, decryption and verification at the Controller layer?

Implementation method:
  • Use filters to intercept requests and uniformly handle request parameters (encryption, decryption, validation, etc.)
  • The custom request object (new class inherits HttpServletRequestWrapper class), the request parameters to filter processing, makes the controller only accept business parameters.

Question: Why do I need to customize the Request object? Since the JSON object passed by the POST request needs to be fetched by the Request object stream, once we call request.getinputStream (), the stream will be automatically closed and the request parameters will no longer be fetched by our Controller layer.

Customize the Request object
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; * @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) { } }; }}Copy the code
Custom filter
/** * @author Longer * @description Gets request parameters and processes them. Signature verification, packet decryption, and parameter filtering. * @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 (" request path: "+ requestURL); String method = request.getMethod(); if (!" POST".equals(method)) {throw new RuntimeException(" not supported yet "+ method +" request method "); RequestWrapper RequestWrapper = new RequestWrapper(request); String bodyString = requestWrapper.getBodyString(); If (stringutils.isEmpty (bodyString)) {throw new RuntimeException(" Request body cannot be empty "); } log.info(" request parameter: "+ bodyString); JsonRequest jsonRequest = JacksonUtil.jsonToBean(bodyString, JsonRequest.class); ParameterValidate (jsonRequest); // parameterValidate(jsonRequest); //step1 determine the validity of the request. Long currentTime = System.currentTimemillis (); long currentTimeMillis(); long subTime = currentTime - jsonRequest.getTimestamp(); long tenMinuteMs = 10 * 60 * 1000; If (subTime < - tenMinuteMs | | subTime > tenMinuteMs) {throw new RuntimeException (" exception a request, the abnormal time "); } String requestUUIDKey = MessageFormat.format(RedisConstant.REQUEST_UUID, jsonRequest.getRequestId()); Object requestFlag = redisUtil.get(requestUUIDKey); if (! Stringutils. isEmpty(requestFlag)) {throw new RuntimeException(" request exception, repeat request "); } redisUtil.set(requestUUIDKey, JacksonUtil.beanToJson(jsonRequest), 15 * 60); Map<String, Object> paramMap = rsautil. bean2Map(jsonRequest); paramMap.remove("sign"); String appIdKey = messageFormat. format(redisconstant.request_appId, jsonRequest.getAppId()); // Obtain the RSA key according to the AppKey. Object ob = redisUtil.get(appIdKey); If (stringutils.isempty (ob)) {throw new RuntimeException(" can't find specified appID "); } String jsonString = (String) ob; JSONObject jsonObject = JSONObject.parseObject(jsonString); Rsa publicKey String publicKey = jsonobject.getstring ("publicKey"); //rsa publicKey String publicKey = jsonobject.getstring ("publicKey"); //rsa privateKey String privateKey = jsonobject.getstring ("privateKey"); // Parameter sort Map<String, Object> sortedMap = rsautil.sort (paramMap); Key1Value1key2Value2 String urlParams = rsautil. groupStringParam(sortedMap); key1Value1key2Value2 String urlParams = rsautil. groupStringParam(sortedMap); / / signature verification Boolean verify = RSAUtil. Verify (HexUtils. HexStringToBytes (urlParams), publicKey, jsonRequest. GetSign ()); if (! Verify) {throw new RuntimeException(" signature verification failed "); } // Private key decryption, Get aseKey String aseKey = RSAUtil. DecryptByPrivateKey (HexUtils. HexStringToBytes (jsonRequest. GetAseKey ()), privateKey); if (! Stringutils.isempty (jsonRequest.getBody())) {// Decrypt request String body = ""; try { body = AESUtil.decrypt(jsonRequest.getBody(), aseKey, jsonRequest.getAppId().substring(16)); } catch (Exception e) {log.error(" error: ",e); Throw new RuntimeException(" request parameter decryption exception "); } / / message passed to the controller layer requestWrapper setBodyString (body. GetBytes (Charset. Class.forname (" utf-8 "))); } // Pass the request filterchain. doFilter(requestWrapper, servletResponse); } private void parameterValidate(JsonRequest jsonRequest) { if (StringUtils.isEmpty(jsonRequest.getAppId())) { throw new RuntimeException(" Parameter exception, appId cannot be null "); } if (stringutils.isempty (jsonRequest.getasekey ())) {throw new RuntimeException(" parameter exception, aseKey cannot be empty "); } if (stringutils.isempty (jsonRequest.getrequestid ())) {throw new RuntimeException(" parameter exception, requestId cannot be empty "); } if (stringutils.isEmpty (jsonRequest.getSign())) {throw new RuntimeException(" parameter exception, sign cannot be empty "); } if (jsonRequest.gettimestamp () == 0l) {throw new RuntimeException(" parameter exception, timestamp cannot be empty "); }}}Copy the code

Complete process demonstration

The caller

HttpClientUtils class
/** * @author: Longer * @description: */ public class HttpClientUtils { private static Logger logger = Logger.getLogger(HttpClientUtils.class.getName()); public static String doPostJson(String url, String json) {/ / create an Httpclient object CloseableHttpClient Httpclient. = HttpClients createDefault (); CloseableHttpResponse response = null; String resultString = ""; Try {// Create an HttpPost request HttpPost HttpPost = new HttpPost(URL); StringEntity entity = new StringEntity(json, contentType.application_json); httpPost.setEntity(entity); Response = httpClient.execute(httpPost); resultString = EntityUtils.toString(response.getEntity(), "utf-8"); } catch (Exception e) { resultString = e.getMessage(); Logger. info(" HTTP access failed: "+ e); } finally { try { response.close(); } catch (IOException e) {logger.info(" Response failed to close: "+ e); } } return resultString; } /** * post request, signature, and packet encryption ** @param url request address * @param json request json parameter * @param appId merchant id * @param publicKey rsa publicKey * @param PrivateKey rsa privateKey * @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 encryption logger.info(" Start aseKey encryption....") ); byte[] enStr = RSAUtil.encryptByPublicKey(aseKey, publicKey); String aseKeyStr = HexUtils.bytesToHexString(enStr); jsonRequest.setAseKey(aseKeyStr); String body = ""; Try {logger.info(" Start request parameter encryption....") ); body = AESUtil.encrypt(json, aseKey, appId.substring(16)); } catch (Exception e) {logger.info(" message encryption Exception: "+ e); Throw new UncheckedException(" packet encryption exception ", e); } jsonRequest.setBody(body); Map<String, Object> paramMap = RSAUtil.bean2Map(jsonRequest); paramMap.remove("sign"); // Parameter sort Map<String, Object> sortedMap = rsautil.sort (paramMap); Key1Value1key2Value2 String urlParams = rsautil. groupStringParam(sortedMap); key1Value1key2Value2 String urlParams = rsautil. groupStringParam(sortedMap); // Private key signature logger.info(" Start parameter signature....") ); String sign = RSAUtil.sign(HexUtils.hexStringToBytes(urlParams), privateKey); jsonRequest.setSign(sign); String requestParams = JacksonUtil.beanToJson(jsonRequest); Logger. info(" Initiate a request...." ); String result = doPostJson(url, requestParams); return result; } public static String getUUID32() { String uuid = UUID.randomUUID().toString(); uuid = uuid.replace("-", ""); return uuid; }}Copy the code
Business parameter object that needs to be passed
@Data
public class RequestDto {
    private String name;
    private int age;
    private String hobby;
}

Copy the code
Send the request
Public static void main (String [] args) {/ / request address 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 publicKey String publicKey = ""; Rsa privateKey String privateKey = ""; HttpClientUtils.doPostJsonForSign(url, json, appId, publicKey, privateKey) }Copy the code

The receiving party

controller
@Slf4j @RestController public class TestController { @RequestMapping("test") public String test(RequestDto requestDto){ Log.info (" received request parameter: "+ Jacksonutil.beantojson (requestDto)); return "a"; }}Copy the code

Because we are treating parameters uniformly, our controller receives parameters from a RequestDto object, not a JsonRequest object

Content to end here, as a developer, there is a learning atmosphere and a communication circle is particularly important, this is my iOS development public number: programming Daxin, whether you are small white or big ox are welcome to enter, let us progress together, common development!