1 问题场景
一些场景中,数据库字段用于存储json格式数据,处于安全的考虑,该json数据中,某些敏感信息字段需要做加密存储,例如身份证号、手机号等。如果将整个json数据进行加密处理,加密后的数据较大,只对需解密的字段进行加密处理更符合实际。
如何实现呢?
之前写过一篇脱敏注解,通过自定义序列化实现的,结合来看,其实可以复用之前的脱敏注解的模式,定义字段加解密注解,通过自定义序列化和反序列化实现。
2 代码实现
- 注解类
import com.test.jsonencrypt.EncryptJsonFiledDeSerializer;
import com.test.jsonencrypt.EncryptJsonFiledSerializer;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <pre>
* 加解密注解,标注在属性上
* </pre>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
// 使用自定义的序列化实现
@JsonSerialize(using = EncryptJsonFiledSerializer.class)
// 使用自定义的序列化实现
@JsonDeserialize(using = EncryptJsonFiledDeSerializer.class)
public @interface EncryptJsonFiled {
}
- 自定义序列化实现类
import com.test.jsonencrypt.annotation.EncryptJsonFiled;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.test.util.EncryUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.util.Objects;
/**
* <pre>
* 序列化注解自定义实现
* JsonDeserializer<String>:指定String类型,deserialize()方法用于将修改后的数据载入
* Jackson使用ContextualSerializer在序列化时获取字段注解的属性
* </pre>
*/
public class EncryptJsonFiledDeSerializer extends JsonDeserializer<String> implements ContextualDeserializer {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String valueAsString = p.getValueAsString();
// 数据不为空时解密数据
if (StringUtils.isNotBlank(valueAsString)) {
// EncryUtil为封装的加解密工具
return EncryUtil.decrypt(valueAsString);
}
return valueAsString;
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
EncryptJsonFiled annotation = property.getAnnotation(EncryptJsonFiled.class);
// 只针对String类型
if (Objects.nonNull(annotation)&&Objects.equals(String.class, property.getType().getRawClass())) {
return this;
}
return ctxt.findContextualValueDeserializer(property.getType(), property);
}
}
- 自定义反序列化实现类
import com.test.jsonencrypt.annotation.EncryptJsonFiled;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.test.util.EncryUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.util.Objects;
/**
* <pre>
* 序列化注解自定义实现
* JsonSerializer<String>:指定String类型,serialize()方法用于将修改后的数据载入
* Jackson使用ContextualSerializer在序列化时获取字段注解的属性
* </pre>
*/
public class EncryptJsonFiledSerializer extends JsonSerializer<String> implements ContextualSerializer {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 数据不为空时加密数据
if (StringUtils.isNotBlank(value)) {
String encrypt = EncryUtil.encry(value);
gen.writeString(encrypt);
return;
}
gen.writeString(value);
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
EncryptJsonFiled annotation = property.getAnnotation(EncryptJsonFiled.class);
// 只针对String类型
if (Objects.nonNull(annotation)&&Objects.equals(String.class, property.getType().getRawClass())) {
return this;
}
return prov.findValueSerializer(property.getType(), property);
}
}
3 测试
- 用于测试的简易实体类
@Data
@Accessors(chain = true)
public class User {
private String name;
private int age;
@EncryptJsonFiled
private String idCard;
}
- 测试类
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* <pre>
* 测试加解密注解
* </pre>
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestEntry {
@Test
public void test() throws JsonProcessingException {
User user = new User().setAge(11)
.setName("小明")
.setIdCard("13523451124");
// 注意使用Jackson的json工具
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(user);
System.out.println("序列化后的json数据:" + json);
User user1 = objectMapper.readValue(json, User.class);
System.out.println("反序列化后的解密数据:" + user1.getIdCard());
}
}
测试结果:
序列化后的json数据:{
"name":"小明","age":11,"idCard":"2f3d8f692eeaac2cbc60423ed99aed63"}
反序列化后的解密数据:13523451124