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 属性值会被加密。 由您决定如何实现encrypt
和decrypt
方法。我认为您可以使用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 数据的主要内容,如果未能解决你的问题,请参考以下文章