最近开发过程中,遇到一个场景。即要在打印日志的时候对json中部分字段进行加密操作(数据传输时不需要加密)。
一下是选定的解决方案。
JAVA项目:
一、使用“注解”配合fastjson的“值过滤器”,实现对字段自动加密。
1.1 创建自定义注解【EncryptionField】。
import java.lang.annotation.*; /** * 用于标识需要加密的字段 */ @Target({ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface EncryptionField { }
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 implements ValueFilter { /** * 加密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(" "); buffer.append("输入:").append(jsonEncryptUtil.jsonEncrypt(requestUrl, requestBody)); buffer.append(" "); 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; }
2.2
using System; namespace ConsoleApp3 { /// <summary> /// 用于标识需要加密的字段 /// </summary> public class EncryptionField : Attribute { } }
using Newtonsoft.Json; using System; using System.Reflection; namespace ConsoleApp3 { public class EncryptionConvert : JsonConverter { public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { bool isNullable = IsNullableType(objectType); Type t = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType; if (reader.TokenType == JsonToken.Null) { if (!IsNullableType(objectType)) { throw new Exception(string.Format("不能转换null value to {0}.", objectType)); } return null; } try { return true; } catch (Exception ex) { throw new Exception(string.Format("Error converting value {0} to type '{1}'", reader.Value, objectType)); } throw new Exception(string.Format("Unexpected token {0} when parsing enum", reader.TokenType)); } /// <summary> /// 判断是否为Bool类型 /// </summary> /// <param name="objectType">类型</param> /// <returns>为bool类型则可以进行转换</returns> public override bool CanConvert(Type objectType) { return true; } public bool IsNullableType(Type t) { if (t == null) { throw new ArgumentNullException("t"); } return (t.BaseType.FullName == "System.ValueType" && t.GetGenericTypeDefinition() == typeof(Nullable<>)); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { writer.WriteStartObject(); if (value == null) writer.WriteNull(); PropertyInfo[] proInfos = value.GetType().GetProperties(); //获取所有属性 foreach (var item in proInfos) { var curField = value.GetType().GetProperty(item.Name); bool isEncryptionField = false; foreach (var attr in curField.CustomAttributes) { if (attr.AttributeType == typeof(EncryptionField)) { writer.WritePropertyName(item.Name); writer.WriteValue("***"); isEncryptionField = true; } } if (!isEncryptionField) { writer.WritePropertyName(item.Name); writer.WriteValue(curField.GetValue(value, null)); } } writer.WriteEndObject(); } } }
static void Main(string[] args) { Console.WriteLine("Hello World!"); Order o = new Order(); o.Name = "黄忠情"; o.Desc = "测试"; var result = JsonConvert.SerializeObject(o, new EncryptionConvert()); Console.WriteLine(result); Console.Read(); }
public class Order
{
[EncryptionField]
public string Name { get; set; }
public string Desc { get; set; }
}