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

加密解密验签名流程演示/** * @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位的uuidString 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);// 拼接参数:key1Value1key2Value2String 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);//拼接参数:key1Value1key2Value2String urlParams2 = RSAUtil.groupStringParam(sortedMap2);//签名验证boolean verify = RSAUtil.verify(HexUtils.hexStringToBytes(urlParams2), publicKey, jsonRequest.getSign());if (!verify) {throw new RuntimeException("签名验证失败");}//私钥解密,获取aseKeyString 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;}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream bais = new ByteArrayInputStream(body);return new ServletInputStream() {@Overridepublic int read() throws IOException {return bais.read();}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}};}}


推荐阅读