在springboot程序中jackson自定義註解和字段解析器

一、需求提出和兩種解決方案

最近有個需求,需要在springboot程序中在返回給前端json串的時候將部分字段加密。在之前的一篇文章中,曾經說過對整個請求體進行加密的方法,可以使用spring擴展的參數解析器做處理:spring mvc請求體偷樑換柱:HandlerMethodArgumentResolver ,那如果想要對返回值中部分字段的值做加密處理呢?這裏想到了兩種方式

  • 使用擴展的參數解析器的方式,在resolveArgument方法中利用反射一一查看相對應的字段需不需要進行加密,如果需要加密則將相應的字段進行加密轉換
  • 使用jackson相關的功能處理,雖然還暫時不知道怎麼處理,但是肯定有辦法。

那就一一分析兩種方式的利弊

1、使用擴展的參數解析器的方式

能處理,但是轉換類型之後沒有相應的類型進行存儲,舉個例子,有個對象如下所示

{
	"code": "",
	"data": {
		"accessToken": "",
		"expireTime": 0
	},
	"msg": ""
}

我想對data字段進行加密,加密前的類型是個具體類型,比如是個TokenResp,加密後是個字符串類型,那麼轉換後的對象整體是擁有code、data字符串和msg字段的對象,可以考慮使用map存儲(雖然沒試驗到底行不行)。可以確定的是使用這種方式會比較複雜,畢竟要使用反射獲取很多字段並取到值,這勢必會破壞原springboot關於序列化功能的完整性。

2、使用jackson相關的功能

在經過參數解析器處理之後,最終參數會被jackson的ObjectMapper轉換成json字符串並返回給調用方,所以從最終結果上來看,jakson是最終執行序列化的工具,從它入手解決這個問題更加合理。google一下相關功能,還真的找到了相關的功能:https://stackoverflow.com/questions/34965201/customize-jackson-objectmapper-to-read-custom-annotation-and-mask-fields-annotat ,使用回答者提供的方式,成功解決了這個問題。

二、使用jackson解決部分序列化字段加密的功能

1、定義註解

首先定義一個註解,該註解加在類上表示整個對象加密,加在字段上表示相應的字段加密

@Documented
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RsaEncrypt {

    /**
     * 是否啓用加密
     *
     * @see
     */
    boolean encrypt() default true;
}

2、繼承標準序列化類

該類的作用是對具體的字段轉換成json字符串後再加密,這裏demo是使用RSA進行了加密和簽名。

@Slf4j
public class RsaDataSerializer extends StdSerializer<object> {

    public RsaDataSerializer() {
        super(Object.class);
    }

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        if (Objects.isNull(value)) {
            return;
        }
        LoginUser principal = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        ClientDetails user = principal.getUser();
        String publicKey = user.getPublicKey();
        RsaProperties bean = SpringUtils.getBean(RsaProperties.class);
        String privateKey = bean.getPrivateKey();
        try {
            String base64Content = RsaUtils.encryptAndSign(ObjectMapperFactory.getObjectMapper().writeValueAsString(value), publicKey, privateKey);
            gen.writeString(base64Content);
        } catch (JsonProcessingException e) {
            log.error("", e);
        }
    }
}

3、綁定序列化類和相關字段的關聯關係

如何確定被自定義註解修飾的某個字段應該用哪個序列化類進行序列化?繼承NopAnnotationIntrospector類,實現findSerializer方法即可。

@Slf4j
public class RsaDataAnnotationIntrospector extends NopAnnotationIntrospector {

    @Override
    public Object findSerializer(Annotated am) {
        RsaEncrypt annotation = am.getAnnotation(RsaEncrypt.class);
        if (annotation != null) {
            return RsaDataSerializer.class;
        }
        return null;
    }
}

4、定製ObjectMapper

做起來非常簡單

@Bean
@Primary
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
    ObjectMapper mapper = builder.createXmlMapper(false).build();
    AnnotationIntrospector sis = mapper.getSerializationConfig().getAnnotationIntrospector();
    AnnotationIntrospector is1 = AnnotationIntrospectorPair.pair(sis, new RsaDataAnnotationIntrospector());
    mapper.setAnnotationIntrospector(is1);
    return mapper;
}

但是要知道其原理。

打開方法定義:JacksonAutoConfiguration.JacksonObjectMapperConfiguration#jacksonObjectMapper(org.springframework.http.converter.json.Jackson2ObjectMapperBuilder)

image-20211027181239692

可以看到,如果我們不定義ObjectMapper對象,則springboot程序會幫我們生成一個默認的,我們只需要根據Jackson2ObjectMapperBuilder對象生成ObjectMapper對象,既不破壞原有功能的完整性,也能自由添加jackson的特性,兩全其美。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章