一、需求提出和兩種解決方案
最近有個需求,需要在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;
}
但是要知道其原理。
可以看到,如果我們不定義ObjectMapper對象,則springboot程序會幫我們生成一個默認的,我們只需要根據Jackson2ObjectMapperBuilder對象生成ObjectMapper對象,既不破壞原有功能的完整性,也能自由添加jackson的特性,兩全其美。