Spring Boot 實現敏感詞及特殊字符過濾處理
背景:
技術采用的是 Spring Boot ,請求方法主要為 POST, 請求使用較多的註解為 @RequestBody
交付測試人員進行測試,測試人員在對模糊搜索模塊進行瞭各種特殊字符的搜索,以至於敏感詞和特殊字符均會入庫。
對於我這樣有情懷的開發者而言,是不能容忍的。
上來就是幹!主要采用
@ControllerAdvice(basePackages = "com.my")
的方式,對用戶提交的數據做處理。
以下是示例代碼,不影響筆者要言表的功能實現:
/** * @author Ryan * @date 2019/4/25 18:41 */ @ControllerAdvice(basePackages = "com.ytkj") public class EscapeSensitiveWordFilter implements RequestBodyAdvice { @Override public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return true; } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { return inputMessage; } @Override public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { if(o != null){ SensitiveWordUtils.apply(o); } return o; } @Override public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return o; } }
由於我們主要針對提交的數據做處理,主要入口在 SensitiveWordUtils.apply(o); 這裡的 “Object ” 參數,其實也就是我們 Controller 方法參數中,打瞭 @RequestBody 的實體。我們可以直接在這裡,使用一些手段做處理即可。
這裡的手段,也隻能使用反射瞭(如果讀者有什麼好的方案可以告訴我)。
1. 字符串替換;
2. 自定義拋出運行時異常;
這樣做的另外一個好處就是,可以在這裡統一管理敏感詞。
如果你使用 replaceAll 的話,統一管理上就比較費勁瞭。
最後,筆者把自己寫的反射放在下面,僅供參考,敏感詞替換部分寫瞭一個“測試“ 作為要替換入口的標記。
歡迎各界大佬來扶正!
import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; /** * @author Ryan * @date 2019/4/26 12:40 */ public class SensitiveWordUtils { /** * @param result * @return */ public static Object apply(Object result) { if (result == null) { return null; } objectParse(result); return result; } /** * @param obj */ public static void objectParse(Object obj) { List<Field> allField = findAllField(obj); for (Field field : allField) { field.setAccessible(true); Class<?> typeClazz = field.getType(); matchFieldType(obj, field, typeClazz); } } public static List<Field> findAllField(Object object){ List<Field> result = new ArrayList<>(); Class<?> clazz = object.getClass(); while (true) { clazz = clazz.getSuperclass(); if (clazz == Object.class) { break; } Field[] declaredFields = clazz.getDeclaredFields(); result.addAll(Arrays.asList(declaredFields)); } return result; } /** * @param obj * @param field * @param clazz */ public static <T> void matchFieldType(Object obj, Field field, T clazz) { try { T param = (T) field.get(obj); if(param == null){ return; } if (clazz == List.class) { List p = (List)param; for (Object o : p) { objectParse(o); } } else if (clazz == String.class) { setValue(obj, field, "測試"); } else if (clazz == Map.class) { Map map = (Map)param; for (Object o : map.keySet()) { objectParse(o); } } } catch (IllegalAccessException e) { e.printStackTrace(); } } /** * * @param object * @param field * @param param * @throws IllegalAccessException */ public static void setValue(Object object, Field field, Object param) throws IllegalAccessException { if(!field.isAccessible()){ throw new IllegalAccessException("modify the field fail."); } field.set(object, param); } }
這裡的 SensitiveWordUtils 還有很大的優化點,我在這裡沒有目前隻是看看效果,寫的很粗糙,望大神不要噴。
讀者自行實現一下,我說一下優化點:
1. 緩存 object 的 String.class 類型的 Field 或者 methodName; 在第一次加載的時候,緩存進去;放到 ConcurrentHashMap<ObjectType, List<StringField>> , 是不是感覺清爽瞭好多;
2. 過濾出來 String 類型的 Field ,其他的類型酌情考慮;
3. 等臣妾的再想想;
Spring Boot 統一敏感詞過濾 demo
對象序列化前的處理
例如springframework框架(responseBody)json 格式:
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyAdviceChain#beforeBodyWrite
中進行對象數據的轉換。
@ControllerAdvice @Slf4j public class ShanDongShengYuHandler implements ResponseBodyAdvice { @Autowired private ObjectMapper objectMapper; @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { ResponseData d = new ResponseData(); sensitiveHidden(body); d.setData(body); return d; } /** * 隻支持自定義類型數據的敏感詞過濾,考慮遞歸性能 */ private void sensitiveHidden(Object body) { if(body==null || StringUtils.isBlank(body.getClass().getName()) || !body.getClass().getName().contains("山東")){ return; } Field[] declaredFields = body.getClass().getDeclaredFields(); for (Field declaredField : declaredFields) { SensitiveWorldHidden annotation = declaredField.getAnnotation(SensitiveWorldHidden.class); log.warn("【註解類型】{}",annotation); try { declaredField.setAccessible(true); Object o = declaredField.get(body); if(annotation != null) { String content = objectMapper.writeValueAsString(o); content = content.replace("垃圾", "**"); Object replaced = objectMapper.readValue(content, o.getClass()); declaredField.set(body, replaced); }else { sensitiveHidden(o); } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (JsonProcessingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } }
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。