各位小伙伴们,有没有遇到这种情况,在信息安全日益受到各方重视的当下,白帽子越来越多,相应的作为开发人员的安全意识也得与日俱增。但是呢,总会有各种各种的原因,会出现全局性系统性的问题,你无法解决,或者无从下手。怎么办?没办法,只有回归本质。
今天分享一个SpringMVC全局文件上传类型限制终极解决方案,为那些还在迷茫该如何处理全局性文件上传漏洞的小伙伴送来一份小礼物。在阅读我的代码之前,请看下面的一篇关于SpringMVC文件上传的博文摘要。博文地址http://exceptioneye.iteye.com/blog/1314958
客户端浏览器将按照 RFC 1867 所规定的格式,对提交表单内容进行编码,服务器端只需要根据 RFC 1867 规定的格式对请求中的信息进行解码,就可以获得客户端表单提交的数据,包括上传的文件。 既然 RFC 1867 所规定的规则是一定的,所以,我们没有必要每次都根据这一规则分析每一次请求中的信息。既然是通用的逻辑,当然也就有通用的类库,比如早期的 jsp smart upload 和 Oreilly 的 COS 类库,以及现在使用最多的 Commons FileUpload 类库。实际开发中,我们只需要使用这些专门针对表单的文件上传处理类库即可。 在实际基于表单的文件上传功能的时候,Spring MVC 框架底层实际上也是使用了以上几种类库。只不过,通过 org.springframework.web.multipart.MultipartResolver 接口的抽象,Spring MVC 将具体选用哪一种类库的权利留给了我们。 MultipartResolver 位于 HandlerMapping 之前,请求一来就交由它来处理。当 Web 请求到达 DispatcherServlet 并等待处理的时候,DispatcherServlet 首先会检查能否从自的 WebApplicationContext 中找到一个名称为 multipartResolver(由 DispatcherServlet 的常量 MULTIPART_RESOLVER_BEAN_NAME 所决定)的 MultipartResolver 实例。如果能够获得一个 MultipartResolver 的实例,DispatcherServlet 将调用 MultipartResolver 的 isMultipart(request) 方法检查当前 Web 请求是否为 multipart类型。如果是,DispatcherServlet 将调用 MultipartResolver 的 resolveMultipart(request) 方法,对原始 request 进行装饰,并返回一个 MultipartHttpServletRequest 供后继处理流程使用(最初的 HttpServletRequest 被偷梁换柱成了 MultipartHttpServletRequest),否则,直接返回最初的 HttpServletRequest。来看看 UML 类图:
MultipartRequest 毕竟是接口,接口就是接口,总得有人实现。AbstractMultipartHttpServletRequest 这个抽象类持有 MultiValueMap<String, MultipartFile> multipartFiles 这样一个实例变量,有了这个 map,把 MultipartRequest 接口里的方法逐一实现就不是难事了。现在的问题是,multipartFiles 从哪来的?不可能像孙悟空似的从石缝里蹦出来吧。。。。。 再回到 MultipartResolver。MultipartResolver 的 isMultipart(request) 方法好实现,当判断出当前的 request 是 multipart 类型的请求,它将调用 MultipartResolve 的 resolveMultipart(request)。这里的 request 就是原始的 HttpServletRequest 对象,奇迹就出现在这里。以 CommonsMultipartResolver 为例,当调用 resolveMultipart(request) 时,看看它是如何创建 MultipartRequest 的:
- public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
- Assert.notNull(request, "Request must not be null");
- if (this.resolveLazily) {
- return new DefaultMultipartHttpServletRequest(request) {
- @Override
- protected void initializeMultipart() {
- MultipartParsingResult parsingResult = parseRequest(request);
- setMultipartFiles(parsingResult.getMultipartFiles());
- setMultipartParameters(parsingResult.getMultipartParameters());
- }
- };
- }
- else {
- MultipartParsingResult parsingResult = parseRequest(request);
- return new DefaultMultipartHttpServletRequest(
- request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters());
- }
- }
OK。其余的内容请移步至对应的文章。
想要全局实现文件上传限制,首先你可能想到的有两种。没错,那就是过滤器和拦截器。但是呢,过滤器玩了半天也
没有实现预期结果,因此拦截方法采用拦截器来实现。
废话不说 直接上代码
SpringMVC拦截器配置:
注意:如果你的文件上传请求比较特殊的话,尽量压缩拦截范围,否则可能影响用户体验。
<mvc:interceptor> <mvc:mapping path="/public/mypath/**/*" /> <bean class="x.x.FileShellInterceptor"></bean> </mvc:interceptor>
SpringMVC请求拦截器:
public class FileShellInterceptor extends HandlerInterceptorAdapter{ @Override public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception { HttpServletRequest req=(HttpServletRequest)request; MultipartResolver res=new org.springframework.web.multipart.commons.CommonsMultipartResolver(); if(res.isMultipart(req)){ // System.out.println("我是文件上传请求"); MultipartHttpServletRequest multipartRequest=(MultipartHttpServletRequest) req; Map<String, MultipartFile> files= multipartRequest.getFileMap(); Iterator<String> iterator = files.keySet().iterator(); while (iterator.hasNext()) { String formKey = (String) iterator.next(); // System.out.println("表单key:"+formKey); MultipartFile multipartFile = multipartRequest.getFile(formKey); if (!ValidateUtils.isEmpty(multipartFile.getOriginalFilename())) { String filename = FileUtils.getDateFileName(multipartFile.getOriginalFilename()); // System.out.println("我是文件"+multipartFile.getOriginalFilename()); if(checkFile(filename)){ return true; }else{ request.getSession().removeAttribute(Global.SESSION_ADMIN_USERNAME); HttpUtils.setActionMessage(request, "尊敬的管理员,您的登录信息已经过期,请重新登录!",ACTION_MSG_TYPE.ERROR ,true); String redirectURL=request.getContextPath()+"/admin/login.jspx"; //解决登录页面 显示在frame中的问题。 HttpUtils.write2Client(response, "<html><head><script>top.location.href='"+redirectURL+"'</script></head></html>"); return false; } } } return true; }else{ return true; } } private boolean checkFile(String fileName){ boolean flag=false; String suffixList="xls,xlsx,jpg,gif,png,ico,bmp,jpeg"; //获取文件后缀 String suffix=fileName.substring(fileName.lastIndexOf(".")+1, fileName.length()); if(suffixList.contains(suffix.trim().toLowerCase())){ flag=true; } return flag; } }
好啦。周五了,咱们都可以安心地回家过周末了。
原文地址:http://blog.csdn.net/zgs_shmily/article/details/45917527
原文地址:http://blog.csdn.net/zgs_shmily/article/details/45917527
时间: 2024-10-03 22:37:59