Java环境下如何在请求到达控制器前拦截校验特殊字符(Hybris场景)
Hybris Storefront入站请求特殊字符校验解决方案
之前方案失效原因
HandlerInterceptor的执行时机晚于Servlet过滤器链,若仅在拦截器中包装请求,此前Hybris内置过滤器可能已经读取过原始请求体,且未将包装后的请求替换到请求链中,后续组件拿到的仍是原始已被消费的请求对象,因此会出现请求体为空的问题。
方案1:过滤器+自定义可重复读请求包装器(推荐全局校验场景)
该方案从Servlet层替换请求对象,彻底解决请求体不可重复读的问题,适配所有类型的POST请求校验。
步骤1:实现可重复读请求包装类
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { private final byte[] cachedBody; public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException { super(request); InputStream requestInputStream = request.getInputStream(); this.cachedBody = StreamUtils.copyToByteArray(requestInputStream); } @Override public ServletInputStream getInputStream() throws IOException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody); return new ServletInputStream() { @Override public boolean isFinished() { return byteArrayInputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) {} @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; } @Override public BufferedReader getReader() throws IOException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody); return new BufferedReader(new InputStreamReader(byteArrayInputStream, getCharacterEncoding())); } public byte[] getCachedBody() { return this.cachedBody; } }
步骤2:实现全局校验过滤器
@Component public class SpecialCharacterValidateFilter extends OncePerRequestFilter { // 特殊字符正则,可按需调整规则 private static final Pattern SPECIAL_CHAR_PATTERN = Pattern.compile("[<>/\\\\|&!@#$%^*()+\\[\\]{}:;\"',?/~`]"); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String contentType = request.getContentType(); // 跳过文件上传、非JSON请求,表单请求可直接通过request.getParameterMap()校验无需读流 if (contentType == null || !contentType.contains(MediaType.APPLICATION_JSON_VALUE) || contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) { filterChain.doFilter(request, response); return; } // 提前包装请求缓存请求体 CachedBodyHttpServletRequest wrappedRequest = new CachedBodyHttpServletRequest(request); // 校验请求体 String body = new String(wrappedRequest.getCachedBody(), StandardCharsets.UTF_8); if (SPECIAL_CHAR_PATTERN.matcher(body).find()) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getWriter().write("{\"code\":\"400\",\"message\":\"请求包含非法特殊字符\"}"); return; } // 校验通过,将包装后的请求传入后续链路 filterChain.doFilter(wrappedRequest, response); } }
步骤3:注册过滤器到Hybris链路
在Hybris的web.xml或者Spring配置中注册该过滤器,优先级设置到RequestContextFilter之前,确保请求在被其他组件读取前完成包装。
方案2:Spring AOP控制器层校验(推荐轻量场景)
该方案完全避免操作请求流,适配Hybris复杂过滤器链的兼容性更好,无需调整原有链路配置。
实现代码示例
@Aspect @Component public class SpecialCharacterValidateAspect { private static final Pattern SPECIAL_CHAR_PATTERN = Pattern.compile("[<>/\\\\|&!@#$%^*()+\\[\\]{}:;\"',?/~`]"); private final ObjectMapper objectMapper = new ObjectMapper(); // 切点替换为你项目控制器的实际包路径 @Pointcut("execution(* com.your.storefront.controllers..*(..))") public void controllerPointcut() {} @Around("controllerPointcut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); Annotation[][] parameterAnnotations = method.getParameterAnnotations(); Object[] args = joinPoint.getArgs(); // 遍历所有RequestBody标注的参数做校验 for (int i = 0; i < parameterAnnotations.length; i++) { for (Annotation annotation : parameterAnnotations[i]) { if (annotation instanceof RequestBody) { String json = objectMapper.writeValueAsString(args[i]); if (SPECIAL_CHAR_PATTERN.matcher(json).find()) { throw new IllegalArgumentException("请求包含非法特殊字符"); } } } } return joinPoint.proceed(); } }
校验不通过的异常可通过全局异常处理器统一封装返回格式。
注意事项
- 表单提交(
application/x-www-form-urlencoded)请求无需读请求流,直接调用request.getParameterMap()获取参数校验即可,不会触发请求体清空 - 特殊字符规则建议统一维护在项目配置文件中,方便后续迭代调整
- 校验逻辑需跳过文件上传类请求,避免读取二进制内容出现乱码误判
内容的提问来源于stack exchange,提问作者Himanshu Jain
相关产品推荐
相关产品推荐

