對於安全性較高的項目來說,對請求數據和返回數據進行加解密是有必要的。
思路
利用@ControllerAdvice註解,攔截請求和返回數據進行加解密,對前端傳過來的參數進行解密,對接口返回數據進行加密。
我的加解密具體流程,主要是和客服端交互,如果是和安卓和iOS交互的注意密鑰的一致性,加解密的過程一定不能錯
- 服務端流程 業務數據json串----》經AES加密後----》再BASE64編碼-----》gzip壓縮
- 客服端流程 base64編碼(decode)----》gzip解壓----》解密------》json串
我的工具類
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author someone
* @create 2019-08-10 9:44
**/
public class AESUtil {
/**
* AES加密字符串
*
* @param content 需要被加密的字符串
* @return 密文
*/
public static String encrypt(String content) {
try {
IvParameterSpec zeroIv = new IvParameterSpec(SecretProperties.API_SECRET.getBytes());
SecretKeySpec key = new SecretKeySpec(SecretProperties.API_SECRET.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, zeroIv);
byte[] encryptedData = cipher.doFinal(content.getBytes("UTF-8"));
/* String gzi = Base64.encode(encryptedData);
byte[] gzis = GZIPUtils.compress(gzi);*/
return Base64.encode(encryptedData);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 解密AES加密過的字符串
*
* @param content AES加密過過的內容
* @return 明文
*/
public static String decrypt(String content) {
try {
byte[] byteMi = Base64.decode(content);
// byte[] byteMi= str2ByteArray(content);
IvParameterSpec zeroIv = new IvParameterSpec(SecretProperties.API_SECRET.getBytes());
SecretKeySpec key = new SecretKeySpec(SecretProperties.API_SECRET.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key,zeroIv);
byte[] decryptedData = cipher.doFinal(byteMi);
/* String decry = GZIPUtils.uncompressToString(decryptedData);*/
return new String(decryptedData,"UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String getSign(String timeStamp, String params) {
return getMD5(timeStamp + SecretProperties.API_KEY + params).toUpperCase();
}
public static void main(String[] args) {
String content = "{\"id\":\"src_1EWhwuGMfj37P6Rx3INyzLLW\",\"expMonth\":\"01\"," +
"\"expYear\":\"22\"}";
/* String content = "{\"userName\":\"15260767080\"}";*/
// 加密
String encrypt = AESUtil.encrypt(content);
String cc ="";
System.out.println("gzip"+GZIPUtils.uncompressToString(Base64.decode(cc)));
String decry = AESUtil.decrypt(GZIPUtils.uncompressToString(Base64.decode(cc)));
String sign = AESUtil.getSign(getTimeStamp(),encrypt);
System.out.println("sign"+sign);
System.out.println("加密:" + encrypt);
System.out.println("解密:" + decry);
}
//對字符串md5加密
public static String getMD5(String str) {
byte[] hash = null;
try {
hash = MessageDigest.getInstance("MD5").digest(str.getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
if ((b & 0xFF) < 0x10) hex.append("0");
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString();
}
public static String getTimeStamp() {
DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String timeStamp = df.format(new Date());
System.out.println(timeStamp);
return "20190411191250";
}
/**
* 字符串轉時間
* @param strDate
* @return
*/
public static Date dateFormat(String strDate){
//注意:SimpleDateFormat構造函數的樣式與strDate的樣式必須相符
SimpleDateFormat sDateFormat=new SimpleDateFormat("yyyyMMddHHmmss"); //加上時間
Date date= null;
//必須捕獲異常
try {
date=sDateFormat.parse(strDate);
System.out.println(date);
} catch(
ParseException px) {
px.printStackTrace();
}
return date;
}
// 兩個日期相減得到的毫秒數
public static long dateDiff(Date beginDate, Date endDate) {
long date1ms = beginDate.getTime();
long date2ms = endDate.getTime();
return date2ms - date1ms;
}
}
接下來主要是用spring的註解來攔截你的請求數據和服務端返回數據,對攔截到數據進行加解密,在這裏我們主要是用@ControllerAdvice(關於這個註解的含義和用法可以自行去了解),這個註解的主要作用是去攔截你的controlle的所有請求
在你的實現類裏面繼承requestBodyAdviceAdapter的方法,攔截你的請求數據
@Slf4j
@ControllerAdvice
@ConditionalOnProperty(prefix = "faster.secret", name = "enabled", havingValue = "true")
@EnableConfigurationProperties({SecretProperties.class})
@Order(1)
public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {
@Autowired
private SecretProperties secretProperties;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
然後再新建一個類,繼承ResponseBodyAdvice,攔截返回數據
@Slf4j
@ControllerAdvice
@ConditionalOnProperty(prefix = "faster.secret", name = "enabled", havingValue = "true")
@EnableConfigurationProperties({SecretProperties.class})
@Order(2)
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private SecretProperties secretProperties;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
在開發中也遇到了一些坑,現在已經在實際的項目中應用,只貼出來了部分代碼,如果遇到什麼問題可以留言一起解決