RSAAES实现接口验签和参数加密
RSA非对称加密
RSA是一种常用的非对称加密算法,加密和加密使用不同的密钥,常用于要求安全性较高的加密场景,比如接口的验签和接口数据的加密与解密。与非对称加密算法对比,其安全性较高,但是加密性能却比较低,不适合高并发场景,一般只加密少量的数据。AES对称加密
AES是一种最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的),加密和解密使用的是相同的密钥。其加密性能好,加密解密速度非常快,内存需求低,适用于经常发送数据的场合。RSA+AES实现接口验签和请求参数的加密与解密
背景:做为程序猿,我们经常需要在我们自己开发的系统上,开发一些接口供第三方调用,那么这个时候,对我们接口的安全性要求就比较高了,尤其是那种需要传输比较私密的信息的时候,其对安全性的要求就更高了。实现思路
调用方:使用AES对称加密算法对业务请求参数进行加密后传输使用RSA非对称加密算法对AES的密钥进行公钥加密后传输使用RSA的私钥对请求参数进行签名
接收方:获取到请求参数后,对参数进行验签和业务参数的解密
问题:为什么要对AES的密钥进行RSA公钥加密后传输?
AES是对称加密算法,加密和解密的密钥都是同一个,为了防止被别人恶意获取到该密钥,然后对我们的业务请求参数进行解密,我们需要将AES密钥进行非对称加密后再进行传输。代码实现 org.bouncycastle bcpkix-jdk15on 1.56 com.fasterxml.jackson.core jackson-databind 2.9.8 org.codehaus.jackson jackson-core-asl 1.8.3 请求和接收的实体对象@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工具类 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工具类 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 init() { Map 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 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 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 sort(Map map) { if (map == null) { return null; } Map result = new TreeMap<>((Comparator) (o1, o2) -> { return o1.compareTo(o2); }); result.putAll(map); return result; } /** * 组合参数 * * @param map * @return 如:key1Value1Key2Value2.... */ public static String groupStringParam(Map map) { if (map == null) { return null; } StringBuffer sb = new StringBuffer(); for (Map.Entry 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 bean2Map(Object obj) { if (obj == null) { return null; } Map 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 sort(Map map) { if (map == null) { return null; } Map result = new TreeMap<>((Comparator) (o1, o2) -> { return o1.compareTo(o2); }); result.putAll(map); return result; } /** * 组合参数 * * @param map * @return 如:key1Value1Key2Value2.... */ public static String groupStringParam(Map map) { if (map == null) { return null; } StringBuffer sb = new StringBuffer(); for (Map.Entry 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 bean2Map(Object obj) { if (obj == null) { return null; } Map 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 + " r解密后:" + decStr); System.out.println(" r"); System.out.println("私钥签名======公钥验证"); String sign = RSAUtil.sign(str.getBytes(), privateKey); System.out.println("签名: r" + sign); boolean flag = RSAUtil.verify(str.getBytes(), publicKey, sign); System.out.println("验签结果: 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; @Slf4j public class JacksonUtil { private static ObjectMapper objectMapper = new ObjectMapper(); /** * 对象转换成json * * @param obj * @param * @return */ public static 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 * @return */ public static 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 * @return */ public static T jsonToBean(String str, Class 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 * @return */ public static List jsonToBeanList(String str, Class 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); } } 加密解密验签名流程演示 public class RequestTest { public static void main(String[] args) { /****先给调用方分配一组RSA密钥和一个appId****/ //初始化RSA密钥 Map init = RSAUtil.init(); //私钥 String privateKey = RSAUtil.getPrivateKey(init); //公钥 String publicKey = RSAUtil.getPublicKey(init); //appId,32位的uuid String appId = getUUID32(); /****先给调用方分配一组RSA密钥和一个appId****/ /*****调用方(请求方)*****/ //业务参数 Map 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 paramMap = RSAUtil.bean2Map(jsonRequest); paramMap.remove("sign"); // 参数排序 Map sortedMap = RSAUtil.sort(paramMap); // 拼接参数:key1Value1key2Value2 String urlParams = RSAUtil.groupStringParam(sortedMap); //私钥签名 String sign = RSAUtil.sign(HexUtils.hexStringToBytes(urlParams), privateKey); jsonRequest.setSign(sign); /*****调用方(请求方)*****/ /*****接收方(自己的系统)*****/ //参数判空(略) //appId校验(略) //本条请求的合法性校验《唯一不重复请求;时间合理》(略) //验签 Map paramMap2 = RSAUtil.bean2Map(jsonRequest); paramMap2.remove("sign"); //参数排序 Map 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; 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: 复制输入流 * * @param inputStream * @return */ 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) { } }; } }自定义过滤器 @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 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 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类 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 paramMap = RSAUtil.bean2Map(jsonRequest); paramMap.remove("sign"); // 参数排序 Map 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
沙雕游戏非常普通的鹿将推出正式版非常普通的鹿将登陆主机平台今日(8月25号),游戏发商GibierGames宣布非常普通的鹿(DEEEERSimulator)将于11月25号推出正式版,并也将登陆SwitchPS4XboxSeriesXS
黑神话悟空被某博主挑刺,称应抵制该游戏,网友中国式家长大家好,我是南巷花猫。近期被黑神话悟空刷屏过后,在大部分玩家期待该作品上线的同时,也出现了一些不好的声音,前有某画师无知吐槽,后又有某博主挑刺,给出的理由更是让人无语,称应该抵制该
玩家评漫威对决最拉跨阵容,蜘蛛侠尴尬上榜激怒铁粉用它没输过对于CCG卡牌游戏的爱好者来说,评选最强卡组或者最弱阵容一直都是他们津津乐道的事情。这不刚刚上线势头正猛的网易漫威对决就有玩家开始评选最拉跨阵容了,在这名玩家数天的体验中,他认为最
峡谷打野上分技巧,澜无限卡刀漂移打法,兰陵王巧用破隐迷惑对手咖啡豆萌好呀在峡谷中掌握一些小技巧,对于上分有极大的帮助,那么都有哪些小技巧呢?赶紧来看看吧一澜无限卡刀澜虽然被削了一波连招速度和大招伤害,但面对脆皮还是非常好用的,推荐连招213
超级好玩的忍者休闲解压游戏hi大家好我kk同学今天给大家介绍一款动作忍者游戏,英文名叫StealthMaster,在谷歌大概有1000W万下载,评分4。2分。它的核心玩法是玩家扮演忍者潜伏到黑帮总部,消灭持
王者荣耀将迎大更新段位重新划分王者变多了作为一款国民级手游,王者荣耀的知名度在年轻人当中非常高。玩过这个游戏的同学都清楚,依靠排位赛机制,玩家可以达到不同的段位,比如白银钻石和最强王者等等,最高则为荣耀王者。12月8日,
年纪大了也能上荣耀王者原来上荣耀王者这么轻松,接触王者也有几年了,平时工作忙也就晚上跟几个朋友开黑玩玩,基本算是社交解压工具了,今年装修的单子比以往少了些,年底收尾闲了就试试看能不能上荣耀,没想到这么轻
第四天灾无所畏惧第四天灾我靠玩家制霸星域作者第0人称姜泽继承了宇宙奇观天启的一切权限。以天启为服务器,召唤天蓝星的小伙伴,成为光能战士。于是,宇宙间,多了一股恶势力。智囊指挥团对方都是一群无脑土著
虎牙猛男杯突围赛结束,各大战队仍有出线机会目前由虎牙举办的猛男杯比赛正在如火如荼的进行中,不过目前的赛况是,突围赛的比赛已经结束,前三名的战队分别是Z4战队,KHG战队ET战队。根据目前的赛况来看,各个战队的积分差距并没有
最新LPL转会咨询IG大换洗Rookie成为自由人各位英雄联盟的观众老爷们大家好,我是阿岩!话不多说,直接上干货!LPL转会期在众多流言蜚语中已步入尾声了,不过越到最后爆炸消息越多。IG的Rookie在微博发布最新消息,声称自己已
DayZ之父跟我们聊了聊他的新作翼星求生IGN独家依旧是一款生存游戏,但这次是外星科幻题材在游戏这个行业,做出一款成功产品,可能是运气但要做出第二款成功产品,就必须要靠实力了如今,DayZ之父DeanHall就带着他的新作翼星求生
老车企,新豪华,上汽奥迪A7L正式投产并预售,如何玩转新市场?今天,上汽奥迪A7L正式宣布投产,并且正式开启预售,此次除了前期亮相的现行版预售价为67。77万元69。77万元,先见版的预售价格为77。77万元,同时今天55TFISI也发布了车
丕平三世的家族背景生平征服者历史影响家族背景丕平三世的成功,与他的祖父和父亲是分不开的。他的祖父赫斯塔尔丕平,排挤了众多凶狠狡诈的敌手,成为墨洛温王朝宫廷唯一的宫相。他的父亲铁锤查理马特,进一步将墨洛温王朝的懒王玩弄
卡尔约翰逊的家族和有往来的帮派家族贝佛利约翰逊卡尔的母亲,在游戏中没有露过面,她在1992年的时候被巴拉斯帮的成员杀死,从此约翰逊家族失去了一位伟大的母亲。也正是因为她的牺牲,卡尔才会重新回到洛杉矶,展开了在圣
里昂家族的基本简介基本简介游戏背景里昂是GTA中最强大的一个黑手党家族,同时也是最强大的帮派之一。第一次出现在侠盗猎车手3中时,里昂家族就构系庞大,不但有惊人的人脉,其地盘可怕的武装也让人胆寒,使玩
路易斯费尔南多洛佩兹的角色经历出场人物角色经历早期生活路易斯费尔南多洛佩兹是多米尼加裔美国人,他的家庭成员有他的母亲阿德里亚娜延伊拉洛佩兹,他的哥哥埃内斯托洛佩兹,和他的妹妹莉塔洛佩兹。路易斯的父亲是一名美国海军陆战队
兰斯万斯的人物经历故事表现事件角色琐事出场任务游戏侠盗猎车手系列中的角色简介兰斯万斯(LanceVance)是动作射击游戏系列侠盗猎车手中的一名角色,在侠盗猎车手罪恶都市游戏中曾作为一名配角出现,而在侠盗猎车手罪恶都市传奇中作
首家智己体验全球首秀,两款新车,除了黑科技还有些什么?当造车新势力点燃了我国新能源车市的战火之后,有人说变革之路已经开启,传统国产汽车将会被取代事实真的是如此吗?当笔者走进魔都首家,同时也是全球首家智己体验店后,发现事实并非如此。智己
大G也玩纯电?奔驰EQG概念车亮相IAA要说今年德国慕尼黑举行的2021IAAMobilityShow上最火的品牌,那一定是奔驰,因为奔驰带来了一辆纯电大G奔驰EQG概念车。大G作为全世界越野车的顶级存在,它的纯电版究竟
特斯拉Model2渲染图曝光,像是飞度和韩系车的结合体特斯拉要造Model2的消息路人皆知,不过这款车的相关信息依然是犹抱琵琶半遮面。近日外媒曝光了一组Model2的渲染图,我们来一起看看这款车可能长了个什么样子?由于有消息称,这款车
汇聚十万信任,成就十分精品,宋PLUS车系用实力成就爆款品质9月27日,第十万辆宋PLUS缓缓驶下比亚迪西安智慧工厂的生产线。自去年9月上市以来,超感座驾宋PLUS凭借悦享颜值奢享空间进享动力智享安全获赞无数,更以创新的新能源技术,持续赋能
14。37万起,东风雪铁龙凡尔赛C5X在车主家发布上市9月23日,预售定单早已破万的东风雪铁龙凡尔赛C5X正式上市,新车售价为14。3718。67万元,更推保值保价政策,搭配选择丰富的超凡金融置换政策,并与预售定单客户和上市后前100