记一次对请求头为multipart/form-data的post请求参数加密操作以及传参
背景:对接一个第三方系统,提供的接口要求对参数进行RSA加密,Base64转码。而且请求头为multipart/form-data。
1、秘钥加密代码
/**
* <p>
* 私钥加密
* </p>
*
* @param data
* 源数据
* @param privateKey
* 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception {
byte[] keyBytes = Base64.decodeBase64(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key priKey = keyFactory.generatePrivate(pkcs8KeySpec);
return decryptOrEncrypt(data, keyFactory, priKey, Cipher.ENCRYPT_MODE, MAX_ENCRYPT_BLOCK);
}
/**
* 对数据进行解密/加密过程
*
* @param data 已加密数据
* @param keyFactory key工厂
* @param key key(pub/pri)
* @param model 加解密模式
* @return
* @throws Exception
*/
private static byte[] decryptOrEncrypt(byte[] data, KeyFactory keyFactory, Key key, int model, int block) throws Exception {
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(model, key);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > block) {
cache = cipher.doFinal(data, offSet, block);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * block;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
2、Base64转码
使用的是org.apache.commons.codec.binary包下的Base64
public static String encodeBase64String(byte[] binaryData) {
return StringUtils.newStringUsAscii(encodeBase64(binaryData, false));
}
3、签名MD5加密
/**
*@param str1 加密方式
*@param str2 需加密字符串
*/
private static String digest(String str1, String str2) {
try {
byte[] Data = str2.getBytes();
MessageDigest Cdigest = MessageDigest.getInstance(str1);
Cdigest.update(Data);
// MD5直 --dig
byte[] dig = Cdigest.digest();
// PrintByteArray("dig", dig);
// byte []形式表达的MD5 转化为 16H形式String表达的MD5
String str16hMD5 = bytesToHexString(dig);
return str16hMD5;
} catch (Exception e) {
return null;
}
}
4、请求头为multipart/form-data,参数需要特殊处理
a.获取连接对象
/**
* 获得连接对象
*
* @param urlStr
* @param headers
* @return
* @throws IOException
*/
private static HttpURLConnection getHttpURLConnection(String urlStr, Map<String, Object> headers) throws IOException {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置超时时间
conn.setConnectTimeout(50000);
conn.setReadTimeout(50000);
//允许输入流
conn.setDoInput(true);
//允许输出流
conn.setDoOutput(true);
//不允许使用缓存
conn.setUseCaches(false);
//请求方式
conn.setRequestMethod("POST");
//设置编码 utf-8
conn.setRequestProperty("Charset", "UTF-8");
//设置为长连接
conn.setRequestProperty("connection", "keep-alive");
//设置其他自定义 headers
if (headers != null && !headers.isEmpty()) {
for (Map.Entry<String, Object> header : headers.entrySet()) {
conn.setRequestProperty(header.getKey(), header.getValue().toString());
}
}
return conn;
}
b、写文件类型的表单参数
/**
* 写文件类型的表单参数
*
* @param paramName 参数名
* @param filePath 文件路径
* @param boundary 分隔符
* @param out
* @throws IOException
*/
private static void writeFile(String paramName, String filePath, String boundary,
DataOutputStream out) {
try (BufferedReader fileReader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath)))) {
/**
* 写分隔符--${boundary},并回车换行
*/
String boundaryStr = BOUNDARY_PREFIX + boundary + LINE_END;
out.write(boundaryStr.getBytes());
/**
* 写描述信息(文件名设置为上传文件的文件名):
* 写 Content-Disposition: form-data; name="参数名"; filename="文件名",并回车换行
* 写 Content-Type: application/octet-stream,并两个回车换行
*/
String fileName = new File(filePath).getName();
String contentDispositionStr = String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"", paramName, fileName) + LINE_END;
out.write(contentDispositionStr.getBytes());
String contentType = "Content-Type: application/octet-stream" + LINE_END + LINE_END;
out.write(contentType.getBytes());
String line;
while ((line = fileReader.readLine()) != null) {
out.write(line.getBytes());
}
//回车换行
out.write(LINE_END.getBytes());
} catch (Exception e) {
LOGGER.error("写文件类型的表单参数异常", e);
}
}
c、写普通的表单参数
/**
* 写普通的表单参数
*
* @param boundary 分隔符
* @param out
* @param entry 参数的键值对
* @throws IOException
*/
private static void writeSimpleFormField(String boundary, DataOutputStream out, Map.Entry<String, Object> entry) throws IOException {
//写分隔符--${boundary},并回车换行
String boundaryStr = BOUNDARY_PREFIX + boundary + LINE_END;
out.write(boundaryStr.getBytes());
//写描述信息:Content-Disposition: form-data; name="参数名",并两个回车换行
String contentDispositionStr = String.format("Content-Disposition: form-data; name=\"%s\"", entry.getKey()) + LINE_END + LINE_END;
out.write(contentDispositionStr.getBytes());
//写具体内容:参数值,并回车换行
String valueStr = entry.getValue().toString() + LINE_END;
out.write(valueStr.getBytes());
}
d、请求
/**
*
* @param url 请求路径
* @param filePathMap 文件参数
* @param keyValues 普通参数
* @return 请求结果
*/
public static String postFormData(String url,Map<String,String> filePathMap,Map<String,Object> keyValues) {
LOGGER.info("发起http请求的url:{}",url);
LOGGER.info("文件请求参数filePathMap:{}",filePathMap);
LOGGER.info("文本参数keyValues:{}",keyValues);
StringBuffer buffer = new StringBuffer();
HttpURLConnection con = null;
try {
con = getHttpURLConnection(url, null);
} catch (IOException e) {
e.printStackTrace();
}
//分隔符,可以任意设置,这里设置为 MyBoundary+ 时间戳(尽量复杂点,避免和正文重复)
String boundary = "MyBoundary" + System.currentTimeMillis();
//设置 Content-Type 为 multipart/form-data; boundary=${boundary}
con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
//发送参数数据
try (DataOutputStream out = new DataOutputStream(con.getOutputStream())) {
//发送普通参数
if (keyValues != null && !keyValues.isEmpty()) {
for (Map.Entry<String, Object> entry : keyValues.entrySet()) {
writeSimpleFormField(boundary, out, entry);
}
}
//发送文件类型参数
if (filePathMap != null && !filePathMap.isEmpty()) {
for (Map.Entry<String, String> filePath : filePathMap.entrySet()) {
writeFile(filePath.getKey(), filePath.getValue(), boundary, out);
}
}
//写结尾的分隔符--${boundary}--,然后回车换行
String endStr = BOUNDARY_PREFIX + boundary + BOUNDARY_PREFIX + LINE_END;
out.write(endStr.getBytes());
out.flush();
out.close();
if (con.getResponseCode() == 200) {
InputStream in = con.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in, "utf-8"));
String temp;
while ((temp = br.readLine()) != null) {
buffer.append(temp);
// buffer.append("\n");
}
in.close();
br.close();
}
} catch (Exception e) {
LOGGER.error("HttpUtils.postFormData 请求异常!", e);
}
LOGGER.info("http请求相应数据:{}", buffer.toString());
return buffer.toString();
}
5、根据业务场景调用方法
public String enrollAdd(EnrollAddVO vo){
Map map = new HashMap(4);
JSONObject requestObj = new JSONObject();
requestObj.put("name",vo.getName());
requestObj.put("sex",vo.getSex());
byte[] requestString = null;
try {
requestString = RsaUtils.encryptByPrivateKey(requestObj.toJSONString().getBytes("UTF-8"),RAS_KEY);
}catch (Exception e){
log.info("RSA加密失败:{}",e.getMessage());
}
String base64Str = Base64.encodeBase64String(requestString);
String sign = MD5Util.genMD5(base64Str + SECRET_KEY);
map.put("appKey",APP_KEY);
map.put("request",base64Str);
map.put("sign",sign);
return HttpClientUtil.postFormData(ENROLL_ADD_URL,null,map);
}