Spring Boot - 加密 JSON 数据

Posted

技术标签:

【中文标题】Spring Boot - 加密 JSON 数据【英文标题】:Spring Boot - Encrypt JSON data 【发布时间】:2017-04-06 01:32:20 【问题描述】:

在我们的应用程序中,我们必须为每个请求和响应加密/解密 Json 属性值(不是属性名称)。 例如,"userName":"encrypted value", "email":"encrypted value"

我们使用 Sprint 启动 1.3,我们使用 @RequestBody@ResponseBody 注释将请求 json 与对象绑定并将响应对象序列化为 JSON。

我们不想在我们的每个控制器方法中调用加密/解密方法。有什么方法可以指示 sprint 在与请求对象绑定之前解密 json 值?同样,在将响应对象字段值转换为 json 之前对其进行加密?或者自定义 Jackson 对我们有帮助吗?

谢谢!

【问题讨论】:

构建一个自定义客户端并在调用服务之前在那里完成 .. 在getter中实现解密,在setter中实现加密呢? 不会闯入或扩展 AbstractJackson2HttpMessageConverter 更具体,因为这是构建 json 响应的地方,也是刷新之前的最后一点? 您找到解决方案了吗?我面临同样的问题,@eparvan 的回答是无关紧要的! 【参考方案1】:

您可以编写自己的 http 消息转换器。由于您使用的是 Spring Boot,这将非常简单:只需从 AbstractHttpMessageConverter 扩展您的自定义转换器并使用 @Component 注释标记该类。

来自spring docs:

您可以通过在 Spring Boot 上下文中简单地添加该类型的 bean 来贡献额外的转换器。如果您添加的 bean 属于默认包含的类型(例如用于 JSON 转换的 MappingJackson2HttpMessageConverter),那么它将替换默认值。

这是一个简单的例子:

@Component
public class Converter extends AbstractHttpMessageConverter<Object> 

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    @Inject
    private ObjectMapper objectMapper;

    public Converter()
        super(MediaType.APPLICATION_JSON_UTF8,
            new MediaType("application", "*+json", DEFAULT_CHARSET));
    

    @Override
    protected boolean supports(Class<?> clazz) 
        return true;
    

    @Override
    protected Object readInternal(Class<? extends Object> clazz,
                                  HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException 
        return objectMapper.readValue(decrypt(inputMessage.getBody()), clazz);
    

    @Override
    protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException 
        outputMessage.getBody().write(encrypt(objectMapper.writeValueAsBytes(o)));
    

    private InputStream decrypt(InputStream inputStream)
        // do your decryption here 
        return inputStream;
    

    private byte[] encrypt(byte[] bytesToEncrypt)
        // do your encryption here 
        return bytesToEncrypt;
    

【讨论】:

如果整个 json 被加密,这将起作用。但在我们的例子中,只有 json 属性值会被加密。 由您决定如何实现encryptdecrypt 方法。我认为您可以使用JsonNode 遍历所有 json 对象值以加密/解密它们。 @eparvan 如果客户端向我发送请求中的密钥(在标头中),我需要用它加密响应怎么办?如何将标头值从 readInternal 传播到 writeInternal 方法? @Jezor 在readInternal 中使用inputMessage.getHeaders(),在writeInternal 中也是如此。 如果少数字段被加密而少数字段未加密怎么办?我们如何区分?【参考方案2】:

好的,所以我使用了@eparvan 的答案并做了一些修改。

    创建一个组件来加密 JSON 响应并解密来自前端的请求参数。

我在类似这样的“数据”对象中以​​加密格式获取请求参数,并以相同的方式发送加密响应数据对象。

参考响应: "数据":"requestOrResponseInEncryptedUsingPrivateKey"

    @Component
    public class Converter extends AbstractHttpMessageConverter<Object> 

        private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

        @Autowired
        private ObjectMapper objectMapper;

        public Converter() 
            super(MediaType.APPLICATION_JSON,
                    new MediaType("application", "*+json", DEFAULT_CHARSET));
        

        @Override
        protected boolean supports(Class<?> clazz) 
            return true;
        

        @Override
        protected Object readInternal(Class<? extends Object> clazz,
                                      HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException 
            return objectMapper.readValue(decrypt(inputMessage.getBody()), clazz);
        

        @Override
        protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException 
            outputMessage.getBody().write(encrypt(objectMapper.writeValueAsBytes(o)));
        

        /**
         * requests params of any API
         *
         * @param inputStream inputStream
         * @return inputStream
         */
        private InputStream decrypt(InputStream inputStream) 
            //this is API request params
            StringBuilder requestParamString = new StringBuilder();
            try (Reader reader = new BufferedReader(new InputStreamReader
                    (inputStream, Charset.forName(StandardCharsets.UTF_8.name())))) 
                int c;
                while ((c = reader.read()) != -1) 
                    requestParamString.append((char) c);
                
             catch (IOException e) 
                e.printStackTrace();
            
            try 
                //replacing /n if available in request param json string

                //reference request: "data":"thisisencryptedstringwithexpirytime"

                JSONObject requestJsonObject = new
                        JSONObject(requestParamString.toString().replace("\n", ""));

                String decryptRequestString = EncryptDecrypt.decrypt(requestJsonObject.getString("data"));
                System.out.println("decryptRequestString: " + decryptRequestString);

                if (decryptRequestString != null) 
return new ByteArrayInputStream(decryptRequestString.getBytes(StandardCharsets.UTF_8));
                 else 
                    return inputStream;
                
             catch (JSONException err) 
                Log.d("Error", err.toString());
                return inputStream;
            
        

        /**
         * response of API
         *
         * @param bytesToEncrypt byte array of response
         * @return byte array of response
         */
        private byte[] encrypt(byte[] bytesToEncrypt) 
            // do your encryption here
            String apiJsonResponse = new String(bytesToEncrypt);

            String encryptedString = EncryptDecrypt.encrypt(apiJsonResponse);
            if (encryptedString != null) 
                //sending encoded json response in data object as follows

                //reference response: "data":"thisisencryptedstringresponse"

                Map<String, String> hashMap = new HashMap<>();
                hashMap.put("data", encryptedString);
                JSONObject jsob = new JSONObject(hashMap);
                return jsob.toString().getBytes();
             else
                return bytesToEncrypt;
        
    

    这是我的 EncryptDecrypt 类,其中正在进行加密和解密

    class EncryptDecrypt 
    
        static String encrypt(String value) 
            try 
                IvParameterSpec iv = new IvParameterSpec(Constants.Encryption.INIT_VECTOR.getBytes(StandardCharsets.UTF_8));
                SecretKeySpec skeySpec = new
                        SecretKeySpec("PRIVATE_KEY_FOR_ENCRYPTION_OR_DECRYPTION"
                        .getBytes(StandardCharsets.UTF_8), "AES");
    
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
    
                byte[] encrypted = cipher.doFinal(value.getBytes());
                byte[] original = Base64.getEncoder().encode(encrypted);
                return new String(original);
             catch (Exception ex) 
                ex.printStackTrace();
            
            return null;
        
    
        static String decrypt(String encrypted) 
            try 
                IvParameterSpec iv = new IvParameterSpec(Constants.Encryption.INIT_VECTOR
                        .getBytes(StandardCharsets.UTF_8));
                SecretKeySpec skeySpec = new SecretKeySpec("PRIVATE_KEY_FOR_ENCRYPTION_OR_DECRYPTION".
                        getBytes(StandardCharsets.UTF_8), "AES");
    
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
                byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
                return new String(original);
             catch (Exception ex) 
                ex.printStackTrace();
            
    
            return null;
        
    

你就完成了!

【讨论】:

当你分享一些代码时,也请分享导入语句。 有没有办法可以防止来自请求到特定路径的响应的这种转换?例如:('/authenticate') 我使用readInternal 方法问题处理加密请求错误显示-[AbstractHandlerExceptionResolver.java:207]-Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'text/plain' not supported] 你遇到过这个问题吗?有什么解决办法吗?

以上是关于Spring Boot - 加密 JSON 数据的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot加密属性文件数据

Spring boot-手把手教你使用Token

Spring Boot 鉴权之—— JWT 鉴权

在spring boot 项目中使用thymeleaf模板

Spring Boot配置文件数据也可以轻松加密?

Spring Boot配置文件数据也可以轻松加密?