You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何配置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 17:19:32