如何配置Jackson ObjectMapper实现AES加密所有字段,仅排除带@DoNotEncrypt注解的字段?
如何配置Jackson ObjectMapper实现AES加密所有字段,仅排除带@DoNotEncrypt注解的字段?
嘿,我来带你一步步实现这个需求——让Jackson的ObjectMapper自动给所有字段做AES加密,只有标了@DoNotEncrypt注解的字段例外。先从你已经定义好的这个注解说起哈:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface DoNotEncrypt { }
第一步:整个靠谱的AES加密工具类
加密解密的核心逻辑得靠它,我给你整个简单的实现示例(注意实际生产环境里,密钥千万别硬编码,要妥善存在配置中心或者密钥管理服务里哈):
import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class AesUtils { private static final String AES_ALGORITHM = "AES"; // 注意:AES密钥长度得是16、24或32字节,这里示例用16字节密钥 private static final String SECRET_KEY = "your_secure_key_here"; public static String encrypt(String value) throws Exception { SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), AES_ALGORITHM); Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encryptedBytes = cipher.doFinal(value.getBytes()); return Base64.getEncoder().encodeToString(encryptedBytes); } public static String decrypt(String encryptedValue) throws Exception { SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), AES_ALGORITHM); Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] decodedBytes = Base64.getDecoder().decode(encryptedValue); byte[] decryptedBytes = cipher.doFinal(decodedBytes); return new String(decryptedBytes); } }
第二步:给Jackson写自定义序列化/反序列化器
咱们得让Jackson知道怎么调用加密工具类处理字段值,所以要写专属的序列化器和反序列化器:
加密序列化器(负责把字段值转成加密字符串)
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import java.io.IOException; public class EncryptSerializer extends StdSerializer<Object> { protected EncryptSerializer() { super(Object.class); } @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { try { // 字段值为null时直接输出null if (value == null) { gen.writeNull(); return; } // 把值转成字符串再加密,你也可以根据实际字段类型调整逻辑 String encryptedValue = AesUtils.encrypt(value.toString()); gen.writeString(encryptedValue); } catch (Exception e) { throw new IOException("加密字段失败", e); } } }
解密反序列化器(负责把加密字符串转回原字段值)
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; public class DecryptDeserializer extends StdDeserializer<Object> { protected DecryptDeserializer() { super(Object.class); } @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { try { String encryptedValue = p.getText(); if (encryptedValue == null || encryptedValue.isEmpty()) { return null; } return AesUtils.decrypt(encryptedValue); } catch (Exception e) { throw new IOException("解密字段失败", e); } } }
第三步:动态指定哪些字段要加密
这是最关键的一步!我们得告诉Jackson:没标@DoNotEncrypt的字段用加密规则,标了的就用默认规则。这时候要用到BeanSerializerModifier和BeanDeserializerModifier来动态修改字段处理器:
序列化规则修改器
import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; import java.util.List; public class EncryptSerializerModifier extends BeanSerializerModifier { @Override public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) { for (BeanPropertyWriter writer : beanProperties) { // 检查字段是否有@DoNotEncrypt注解,没有就替换成加密序列化器 if (writer.getAnnotation(DoNotEncrypt.class) == null) { writer.assignSerializer(new EncryptSerializer()); } } return beanProperties; } }
反序列化规则修改器
import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.DeserializationConfig; import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import java.util.List; public class DecryptDeserializerModifier extends BeanDeserializerModifier { @Override public List<SettableBeanProperty> changeProperties(DeserializationConfig config, BeanDescription beanDesc, List<SettableBeanProperty> beanProperties) { for (SettableBeanProperty property : beanProperties) { // 检查字段是否有@DoNotEncrypt注解,没有就替换成解密反序列化器 if (property.getAnnotation(DoNotEncrypt.class) == null) { property.setValueDeserializer(new DecryptDeserializer()); } } return beanProperties; } }
第四步:配置ObjectMapper,整合所有组件
最后把咱们写的这些组件都注册到ObjectMapper里,这样它就能按咱们的规则干活了:
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; public class ObjectMapperConfig { public static ObjectMapper getEncryptObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); // 注册序列化器、反序列化器和规则修改器 module.addSerializer(new EncryptSerializer()); module.addDeserializer(Object.class, new DecryptDeserializer()); module.setSerializerModifier(new EncryptSerializerModifier()); module.setDeserializerModifier(new DecryptDeserializerModifier()); objectMapper.registerModule(module); return objectMapper; } }
来测试一下效果!
整个实体类试试水,看看加密规则是不是生效:
static class User { private String username; // 这个字段会被加密 @DoNotEncrypt private String plainTextNote; // 这个字段保持明文 private String email; // 这个字段会被加密 // 记得加构造器、getter和setter哦 public User(String username, String plainTextNote, String email) { this.username = username; this.plainTextNote = plainTextNote; this.email = email; } // getter和setter省略,自己补上就行 }
写个测试代码跑一跑:
public class TestMain { public static void main(String[] args) throws Exception { ObjectMapper mapper = ObjectMapperConfig.getEncryptObjectMapper(); // 序列化:把User对象转成JSON User user = new User("zhangsan", "这是明文备注", "zhangsan@example.com"); String json = mapper.writeValueAsString(user); System.out.println("序列化后的JSON:" + json); // 反序列化:把JSON转成User对象 User deserializedUser = mapper.readValue(json, User.class); System.out.println("反序列化后的用户名:" + deserializedUser.getUsername()); System.out.println("反序列化后的明文备注:" + deserializedUser.getPlainTextNote()); } }
运行之后你会发现,username和email字段是加密后的字符串,而plainTextNote还是原来的明文,反序列化后也能正常解密回原内容,完美符合需求!
备注:内容来源于stack exchange,提问作者Nicholas DiPiazza
相关产品推荐
相关产品推荐

