1. 程式人生 > 實用技巧 >對json中指定欄位進行加密操作

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