• 对json中指定字段进行加密操作


    最近开发过程中,遇到一个场景。即要在打印日志的时候对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();
            }
        }
    }
    View Code
            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; }
    }

     
  • 相关阅读:
    解决在Apple Silicon (M1)安装php MongoDB扩展失败
    dyld: Library not loaded: /usr/local/opt/libpng/lib/libpng16.16.dylib
    docker自建bitwarden_rs服务器更新支持send功能
    centos安装puppeteer遇到的报错及解决方案
    Centos宝塔安装NextCloud
    苹果设备型号代码(更新至iPhone12)
    electron内使用vue-slider-component组件报“$attrs is readonly”错误
    ZSH隐藏命令行前面的用户名和主机名
    Android9.0配置charles的https抓包
    记一次discuz修改首页图片路径问题
  • 原文地址:https://www.cnblogs.com/ariter/p/14254342.html
Copyright © 2020-2023  润新知