對json中指定欄位進行加密操作
阿新 • • 發佈:2021-01-09
最近開發過程中,遇到一個場景。即要在列印日誌的時候對json中部分欄位進行加密操作(資料傳輸時不需要加密)。
一下是選定的解決方案。
JAVA專案:
一、使用“註解”配合fastjson的“值過濾器”,實現對欄位自動加密。
1.1建立自定義註解【EncryptionField】。
import java.lang.annotation.*; /** * 用於標識需要加密的欄位 */ @Target({ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interfaceEncryptionField { }
1.2 建立自定義json過濾器繼承自ValueFilter【EncryptionFieldFilter】。
import com.alibaba.fastjson.serializer.ValueFilter; import org.eclipse.jgit.util.StringUtils; import java.lang.reflect.Field; import java.util.Objects; /** * 對使用【EncryptionField】標註的欄位加密 */ public class EncryptionFieldFilter implementsValueFilter { /** * 加密key */ private String encryptKey; public EncryptionFieldFilter(String encryptKey) { this.encryptKey = encryptKey; } @Override public Object process(Object object, String name, Object value) { try { Field field = object.getClass().getDeclaredField(name);if (Objects.isNull(value) || String.class != field.getType() || (field.getAnnotation(EncryptionField.class)) == null) { return value; } if (String.class == field.getType() && StringUtils.isEmptyOrNull(field.toString())) { return value; } return EencryptionUtil.encrypt(value.toString(), encryptKey); } catch (Exception e) { } return value; } }
1.3 直接使用,或者在日誌aop中使用。
JSONObject.toJSONString(preLoanRequestDTO, new EncryptionFieldFilter(encryptKey))
二、自定義方法解構json,對設定的欄位集合進行加密。算是笨方法,不過可以處理複雜結構的json,也不用將json轉為物件就可以操作。
2.1 定義解構方法,及要操作的欄位集合。因為日誌經常放在aop中,這裡把url作為入參,每個介面對應各自的加密節點。
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject;import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.*; @Component public class JsonEncryptUtil { // 加密key @Value("${EncryptionKey}") private String encryptKey; // 需要加密的日誌節點 private static Map<String, List<String>> encryptNodeMap = new HashMap<>(){ { put("order/submit", Arrays.asList( // 進件介面 "customerInfo.name", "customerInfo.identityNo", "customerInfo.mobile","cardInfo.creditCardNo", "cardInfo.cashCardNo"); put("order/check", Arrays.asList( // 驗證介面 "name", "identityNo")); } }; /** * 文字加密(忽略異常) * * @param text 入參 * @return 加密字串 */ public String stringEncrypt(String text) { try { if (!StringUtils.isBlank(text)) { text = EencryptionUtil.encrypt(text, encryptKey); } } catch (Exception e) { text = "文字加密異常:" + e.getMessage() + "加密前資訊:" + text; } return text; } /** * json指定節點加密 * * @param json 入參 * @return 加密字串 */ public String jsonEncrypt(String url, String json) { String result = json; try { if (!StringUtils.isBlank(json)) { for (String key : encryptNodeMap.keySet()){ if(url.toLowerCase().endsWith(key.toLowerCase())){ result = GetAesJToken(JSON.parseObject(json.trim()), encryptNodeMap.get(key)).toString(); } } } } catch (Exception e) { result = "日誌加密異常:" + e.getMessage() + "加密前資訊:" + json; } return result; } /** * 根據節點逐一展開json物件並進行加密 * * @param object 入參 * @param nodeList 入參 * @return 結果 */ private Object GetAesJToken(Object object, List<String> nodeList) { // 如果為空,直接返回 if (object == null || nodeList.size() == 0) return object; JSONObject jsonObject = null; // 多層節點遞迴展開,單層節點直接加密 Map<String, List<String>> deepLevelNodes = new HashMap<>(); for (var node : nodeList) { var nodeArr = Arrays.asList(node.split("\\.")); if (nodeArr.size() > 1) { if (deepLevelNodes.containsKey(nodeArr.get(0))) deepLevelNodes.get(nodeArr.get(0)).add(com.ctrip.framework.apollo.core.utils.StringUtils.join(nodeArr.subList(1, nodeArr.size()), ".")); else deepLevelNodes.put(nodeArr.get(0), new ArrayList<>(Arrays.asList(com.ctrip.framework.apollo.core.utils.StringUtils.join(nodeArr.subList(1, nodeArr.size()), ".")))); } else { object = JsonNodeToAes(object, node); } } if (deepLevelNodes.size() > 0) { for (String key : deepLevelNodes.keySet()) { if (JSON.isValidObject(object.toString())) { var jObject = JSON.parseObject(object.toString()); if (jObject.get(key) != null) { jObject.put(key, GetAesJToken(jObject.get(key), deepLevelNodes.get(key))); } object = jObject; } if (JSON.isValidArray(object.toString())) { var jArray = JSON.parseArray(object.toString()); for (int i = 0; i < jArray.size(); i++) { JSONObject curObject = jArray.getJSONObject(i); if (curObject != null && curObject.get(key) != null) { jArray.set(i, GetAesJToken(curObject.get(key), deepLevelNodes.get(key))); } } object = jArray; } } } return object; } /** * 將確定節點加密 * * @param object 入參 * @param node 入參 * @return 結果 */ private Object JsonNodeToAes(Object object, String node) { if (object == null) return object; if (JSON.isValidObject(object.toString())) { var jObject = JSON.parseObject(object.toString()); if (jObject.get(node) != null) { if (JSON.isValidArray(jObject.get(node).toString())) { var jArray = jObject.getJSONArray(node); for (int i = 0; i < jArray.size(); i++) { jArray.set(i, stringEncrypt(jArray.get(i).toString())); } jObject.put(node, jArray); } else if (!JSON.isValidObject(jObject.get(node).toString())) { jObject.put(node, stringEncrypt(jObject.get(node).toString())); } } object = jObject; } else if (JSON.isValidArray(object.toString())) { var jArray = JSON.parseArray(object.toString()); for (int i = 0; i < jArray.size(); i++) { Object curObject = jArray.getJSONObject(i); if (curObject != null) { jArray.set(i, JsonNodeToAes(curObject, node)); } } object = jArray; } else { object = stringEncrypt(object.toString()); } return object; } }
2.2 如果列印日誌在靜態方法中,可以通過以下方式注入依賴。正常使用的場景就不列舉了。
public static JsonEncryptUtil jsonEncryptUtil; @Resource public void setJsonEncryptUtil(JsonEncryptUtil service) { jsonEncryptUtil = service; } @Setter public static class ApiLog { private Long startTime; private Long endTime; private Long consumeTime; private String requestUrl; private String requestMethod; private String requestIp; private String apiClassName; private String apiMethodName; private String requestBody; private String responseBody; public static ApiLog newInstance() { return new ApiLog(); } private ApiLog() { startTime = System.currentTimeMillis(); } public void setEndTime(Long endTime) { this.endTime = endTime; this.consumeTime = this.endTime - this.startTime; } @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("耗時:").append(consumeTime).append("ms").append(" , "); buffer.append("URL:").append(requestUrl).append(" , "); buffer.append("IP:").append(requestIp).append(" , "); buffer.append("方式:").append(requestMethod).append(" , "); buffer.append("請求方法:").append(apiClassName).append(".").append(apiMethodName).append(" , "); buffer.append("\r\n"); buffer.append("輸入:").append(jsonEncryptUtil.jsonEncrypt(requestUrl, requestBody)); buffer.append("\r\n"); buffer.append("輸出:").append(responseBody); return buffer.toString(); } }
.NET 專案:
C#的暫不細說了,和java一樣理解就好。下面附上json解構方法。
/// <summary> /// 要加密的日誌節點(區分大小寫) /// </summary> public static List<string> aesLogNodes = new List<string> { "identityNo", "name", "oldCashCardNo", "newCashCardNo", "mobile", "data.idNo", "data.userName", "data.mobile", "data.baseInfo.userName", "data.baseInfo.idNo", "data.baseInfo.registerMobile", }; /// <summary> /// json串指定欄位加密 /// </summary> /// <param name="json"></param> /// <param name="aesKey"></param> /// <returns></returns> public static string ToJsonAes(this string json, string aesKey) { try { var jToken = JsonConvert.DeserializeObject<JToken>(json ?? ""); return GetAesJToken(jToken, aesLogNodes, aesKey).ToJson(); } catch (System.Exception ex) { return $"日誌加密失敗:{ex.Message}。原文:{json ?? ""}"; } } /// <summary> /// json串指定欄位加密 /// </summary> /// <param name="json"></param> /// <param name="aesKey"></param> /// <returns></returns> public static string ToJsonAes(this object json, string aesKey) { try { var jToken = JToken.FromObject(json ?? ""); return GetAesJToken(jToken, aesLogNodes, aesKey).ToJson(); } catch (System.Exception ex) { return $"日誌加密失敗:{ex.Message}。原文:{(json ?? "").ToJson()}"; } } /// <summary> /// 根據節點逐一展開json物件並進行加密 /// </summary> /// <param name="jToken"></param> /// <param name="nodeList"></param> /// <param name="aesKey"></param> /// <returns></returns> private static JToken GetAesJToken(JToken jToken, List<string> nodeList, string aesKey) { if (jToken == null || jToken.Type == JTokenType.Null || (nodeList?.Count ?? 0) == 0) return jToken; // 如果當前節點是json字串格式,直接轉為json物件 if (jToken.Type == JTokenType.String && ((jToken.ToString().Trim().StartsWith("{") && jToken.ToString().Trim().EndsWith("}")) || (jToken.ToString().Trim().StartsWith("[") && jToken.ToString().Trim().EndsWith("]")))) { jToken = JsonConvert.DeserializeObject<JToken>(jToken.ToString()); } // 多層節點遞迴展開,單層節點直接加密 Dictionary<string, List<string>> deepLevelNodes = new Dictionary<string, List<string>>(); foreach (var node in nodeList) { var nodeArr = node.Split('.'); if (nodeArr.Length > 1) { if (deepLevelNodes.ContainsKey(nodeArr.First())) deepLevelNodes[nodeArr.First()].Add(string.Join(".", nodeArr.Skip(1))); else deepLevelNodes.Add(nodeArr.First(), (new List<string> { string.Join(".", nodeArr.Skip(1)) })); } else { jToken = JsonNodeToAes(jToken, node, aesKey); } } if (deepLevelNodes.Count > 0) { foreach (var deep in deepLevelNodes) { if (jToken.Type == JTokenType.Object) { if (jToken[deep.Key] != null) jToken[deep.Key] = GetAesJToken(jToken[deep.Key], deep.Value, aesKey); } if (jToken.Type == JTokenType.Array) { for (int i = 0; i < jToken.Count(); i++) { if (jToken[i][deep.Key] != null) jToken[i] = GetAesJToken(jToken[i][deep.Key], deep.Value, aesKey); } } } } return jToken; } /// <summary> /// 將確定節點加密 /// </summary> /// <param name="jToken"></param> /// <param name="node"></param> /// <param name="aesKey"></param> /// <returns></returns> private static JToken JsonNodeToAes(JToken jToken, string node, string aesKey) { if (jToken == null) return jToken; // 如果當前節點是json字串格式,直接轉為json物件 if (jToken.Type == JTokenType.String && ((jToken.ToString().Trim().StartsWith("{") && jToken.ToString().Trim().EndsWith("}")) || (jToken.ToString().Trim().StartsWith("[") && jToken.ToString().Trim().EndsWith("]")))) { jToken = JsonConvert.DeserializeObject<JToken>(jToken.ToString()); } if (jToken.Type == JTokenType.String) { jToken = jToken.ToString().AesEncrypts(aesKey, WebModel.Enums.EncryptionType.Aes); } if (jToken.Type == JTokenType.Object) { if (jToken[node] != null && jToken[node].Type == JTokenType.String) { jToken[node] = jToken[node].ToString().AesEncrypts(aesKey, WebModel.Enums.EncryptionType.Aes); } if (jToken[node] != null && jToken[node].Type == JTokenType.Array) { for (int i = 0; i < jToken[node].Count(); i++) { if (jToken[node][i] != null && jToken[node][i].Type == JTokenType.String) jToken[node][i] = jToken[node][i].ToString().AesEncrypts(aesKey, WebModel.Enums.EncryptionType.Aes); } } } if (jToken.Type == JTokenType.Array) { for (int i = 0; i < jToken.Count(); i++) { jToken[i] = JsonNodeToAes(jToken[i], node, aesKey); } } return jToken; }