对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("\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;
        }

 

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; }
}

 

 

posted @ 2021-01-09 10:58  Ariter  阅读(3283)  评论(0编辑  收藏  举报