#接口统一脱敏处理
前言
最近 在做一个关于数据脱敏的需求,就是将接口返回的一些敏感信息用*代替,对用户的敏感数据予以保护,因为线上出现了用户账号申诉被盗的情况
其实这样一个需求,做起来是是蛮简单的,在前端交互支持的情况下,我们在接口数据序列化之前把它替换掉就可以了。但是因为它是一个相对通用的业务,所以考虑将它做到基础框架里面去。
思路
基本思路:拿到原始待序列化的对象,通过自定义注解的方式,对不同的字段进行相应的脱敏处理
https://github.com/DannyHoo/desensitized 这里前人有一个不错的思路,基本就是这样实现的,但是他使用的是ResponseBodyAdvice结合注解来处理接口和对象,所有的接口(包括非序列化的接口)都要走到这个逻辑里面,性能必定会受到一些影响,而且为了不对原始对象处理,采用了对象反射和深拷贝使得性能会大大降低。但是在并发不高的情况下作为一个全局的脱敏处理框架还是可行的。
扩展
但是我要应对的场景,并发还是挺高的,峰值tps目前在150左右,虽然不是每个接口的调用都会有大并发,但是既然要做成框架性的东西,就不能确定使用方的调用频率了,因此需要换一种更简单的思路来实现以提高性能。
对于springmvc来说,其实已经帮我们做了一次序列化操作,因此我们可以利用这个序列化过程,直接定制处理,不用手动再去处理一次,因此我使用com.fasterxml.jackson序列化来实现这个功能。
另外对于方法的控制,我使用拦截器和线程上下文来处理,被拦截要加密的接口会设置脱敏上下文,包括国际化信息等等,在处理结束后记得清理,以免造成线程污染。
show me the code
1 | /** |
2 | * @Description 脱敏注解 |
3 | * @Author Storm |
4 | * @date 2020.11.25 17:00 |
5 | */ |
6 | @Target({ElementType.FIELD}) |
7 | @Retention(RetentionPolicy.RUNTIME) |
8 | @JacksonAnnotationsInside |
9 | @JsonSerialize(using = DesensitizedSerializer.class) |
10 | public @interface Desensitized { |
11 |
|
12 | /*脱敏类型(规则)枚举*/ |
13 | SensitiveTypeEnum type(); |
14 | } |
1 | /** |
2 | * @Description 脱敏序列化器,根据属性上的注解和上下文信息进行脱敏,使用时注意上下文的设置和清理,避免线程污染!! |
3 | * @Author Storm |
4 | * @date 2020.11.25 17:10 |
5 | */ |
6 | public class DesensitizedSerializer extends StdSerializer<String> implements ContextualSerializer { |
7 |
|
8 | private static final long serialVersionUID = 1L; |
9 |
|
10 | private SensitiveTypeEnum type; |
11 |
|
12 | protected DesensitizedSerializer(Class<String> t) { |
13 | super(t); |
14 | } |
15 |
|
16 | protected DesensitizedSerializer() { |
17 | super(String.class); |
18 | } |
19 |
|
20 | protected DesensitizedSerializer(SensitiveTypeEnum type) { |
21 | super(String.class); |
22 | this.type = type; |
23 | } |
24 |
|
25 | @Override |
26 | public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) { |
27 | //获取注解上下文,拿到配置的脱敏类型 |
28 | Desensitized annotation = beanProperty.getAnnotation(Desensitized.class); |
29 | if (annotation == null) { |
30 | return new DesensitizedSerializer(); |
31 | } |
32 | SensitiveTypeEnum type = annotation.type(); |
33 | return new DesensitizedSerializer(type); |
34 | } |
35 |
|
36 | @Override |
37 | public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { |
38 | DesensitizeContext context = DesensitizeContext.getContext(); |
39 | if (context == null || !context.getNeedDesensitize()) { |
40 | //不需要进行脱敏处理 |
41 | jsonGenerator.writeString(s); |
42 | return; |
43 | } |
44 | String language = UserContext.get().getHl(); |
45 | jsonGenerator.writeString(DesensitizedUtils.desensitize(type, s, language));//根据类型进行具体的脱敏处理,回写到对象中 |
46 | } |
1 | /** |
2 | * @Description 脱敏上下文信息 |
3 | * @Author Storm |
4 | * @date 2020.11.25 17:48 |
5 | */ |
6 | public class DesensitizeContext { |
7 | public static final ThreadLocal<DesensitizeContextObj> context = new ThreadLocal<>(); |
8 |
|
9 | @Data |
10 | public static class DesensitizeContextObj { |
11 | /** |
12 | * 语言 zh en |
13 | */ |
14 | private String language = Locale.ENGLISH.getLanguage(); |
15 |
|
16 | /** |
17 | * 是否需要脱敏 |
18 | */ |
19 | private boolean needDesensitize = false; |
20 |
|
21 | public DesensitizeContextObj() { |
22 | } |
23 |
|
24 | public DesensitizeContextObj(String language, boolean needDesensitize) { |
25 | this.language = language; |
26 | this.needDesensitize = needDesensitize; |
27 | } |
28 | } |
29 |
|
30 |
|
31 | public static DesensitizeContextObj getContext() { |
32 | return context.get(); |
33 | } |
34 |
|
35 | public static void setContext(DesensitizeContextObj obj) { |
36 | context.set(obj); |
37 | } |
38 |
|
39 | public static void removeContext() { |
40 | if (context.get() == null) { |
41 | return; |
42 | } |
43 | context.remove(); |
44 | } |
45 | } |
1 | public class DesensitizeInterceptor extends HandlerInterceptorAdapter { |
2 |
|
3 | @Override |
4 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { |
5 | //设置脱敏上下文 |
6 | DesensitizeContext.setContext(true); |
7 | return true; |
8 | } |
9 |
|
10 | @Override |
11 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { |
12 | //清除脱敏上下文 |
13 | DesensitizeContext.removeContext(); |
14 | } |
15 | } |
1 | @Configuration |
2 | class MvcConfiguration extends WebMvcConfiguration { |
3 | @Override |
4 | protected void addInterceptors(InterceptorRegistry registry) { |
5 | super.addInterceptors(registry); |
6 | registry.addInterceptor(desensitizeInterceptor()).addPathPatterns("/**"); |
7 | } |
8 |
|
9 | } |
1 | //使用 |
2 | @Desensitized(type = SensitiveTypeEnum.EMAIL) |
3 | private String email; |