Java Web 文件上传和下载

前言

Java Web 三大组件已经学完了,今天学习Java代码实现文件的上传和下载,也是经常出现漏洞的一个关键点。

上传

创建上传表单

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

  
    文件上传
  
  
  
用户名:
头像:

实现文件上传需要导入两个jar包。

  • commons-fileupload-1.2.2.jar
  • commons-io-2.11.0.jar
    再配置web.xml,注册一个servlet
    
        UploadServlet
        com.atguigu.java.UploadServlet
    
    
        UploadServlet
        /upload
    

UploadServlet 代码如下:

package com.atguigu.java;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

/**
 * @author cseroad
 */
public class UploadServlet extends HttpServlet {
    /**
     * 处理上传页面
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /**
         * * 导入commons-fileupload、commons-io 两个jar包
         * 解析上传的数据
         */
        // 判断上传的数据是否是多段数据
        if(ServletFileUpload.isMultipartContent(req)){
            // 创建FileItemFactory 工厂实现类
            FileItemFactory fileItemFactory = new DiskFileItemFactory();
            // 创建用于解析上传数据的工具类servletFileUpload
            ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
            // 解析上传的数据。得到每一个表单的fileitem
            servletFileUpload.setHeaderEncoding("UTF-8");
            // 处理乱码
            try {
                List list = servletFileUpload.parseRequest(req);
                // 解析form表单中的每个字段的数据,并将它们分别包装成独立的FileItem对象,区分是普通还是上传
                for (FileItem item : list){
                    if (item.isFormField()){
                        // 普通表单
                        System.out.println("表单项的name属性:"+item.getFieldName());
                        System.out.println("表单项的value属性值:"+item.getString("UTF-8"));//处理乱码
                    } else {
                        // 文件表单
                        System.out.println("表单项的name属性:"+item.getFieldName());
                        String uploadfilename = item.getName();
                        System.out.println("上传的文件名:"+uploadfilename);
                        // 判断文件名是否存在
                        if (uploadfilename.trim().equals("") || uploadfilename == null){
                            continue;
                        }
                        //获取上传的文件名 1.txt
                        String fileName = uploadfilename.substring(uploadfilename.lastIndexOf("/") + 1);
                        //获得文件的后缀名
                        String fileExtName = uploadfilename.substring(uploadfilename.lastIndexOf(".") + 1);
                        //可以使用UUID(唯一识别的通用码),保证文件名唯一
                        String uuidFileName= UUID.randomUUID().toString();
                        System.out.println("保存的文件名:"+uuidFileName+"."+fileExtName);
                        // 1. 第一种保存方法
                        item.write(new File("/Users/cseroad/server/apache-tomcat-7.0.107/webapps/"+uuidFileName+"."+fileExtName));
                    }
                        // 2. 第二种存方法字节流
                        InputStream inputStream = item.getInputStream();
                        FileOutputStream fileOutputStream = new FileOutputStream("/Users/cseroad/server/apache-tomcat-7.0.107/webapps/"+uuidFileName+"."+fileExtName);
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len=inputStream.read(buffer) )> 0){
                            fileOutputStream.write(buffer,0,len);
                        }
                        fileOutputStream.close();
                        inputStream.close();
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }

}

一般保存的路径是设置在当前工程下

String fileSaveRootPath = this.getServletContext().getRealPath("/file");
item.write(new File(fileSaveRootPath+"//"+uuidFileName+"."+fileExtName));

以上代码用了两种方式实现上传的文件进行保存到本地。
一种直接调用FileItem对象的write()方法保存

item.write(new File("/Users/cseroad/server/apache-tomcat-7.0.107/webapps/"+uuidFileName+"."+fileExtName));

一种字节流的方式保存

InputStream inputStream = item.getInputStream();
FileOutputStream fileOutputStream = new FileOutputStream("/Users/cseroad/server/apache-tomcat-7.0.107/webapps/"+uuidFileName+"."+fileExtName);
byte[] buffer = new byte[1024 * 1024];
int len;
while ((len=inputStream.read(buffer) )> 0){
fileOutputStream.write(buffer,0,len);
}
fileOutputStream.close();
inputStream.close();
image.png

下载

创建表单点击下载

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

  
    文件下载
  
  
  
下载:

再配置web.xml,同样注册一个servlet。

    
        Download
        com.atguigu.java.Download
    
    
        Download
        /download
    

Download 代码如下:

package com.atguigu.java;

import org.apache.commons.io.IOUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.UUID;

/**
 * @author cseroad
 */
public class Download extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 读取下载的文件内容
        String downloadFileName =  req.getParameter("filename");
        // 获取web应用的ServletContext对象
        ServletContext servletContext = getServletConfig().getServletContext();
        InputStream resourceAsStream = servletContext.getResourceAsStream("/file/"+downloadFileName);
        // 告诉客户端返回的数据类型
        String mimeType = servletContext.getMimeType("/file/" + downloadFileName);
        resp.setContentType(mimeType);
        resp.setCharacterEncoding("UTF-8");
        String uuidFileName= UUID.randomUUID().toString();
        String new_filename = uuidFileName+".jpg";
        // 告诉客户端收到的数据用于下载使用
        //resp.setHeader("Content-Disposition","attachment;filename="+downloadFileName);
        // 处理中文编码
        resp.setHeader("Content-Disposition","attachment;filename="+URLEncoder.encode(new_filename,"UTF-8"));
        // 获取响应的输出流
        ServletOutputStream outputStream = resp.getOutputStream();
        // 复制流中数据
        IOUtils.copy(resourceAsStream,outputStream);
    }
}

这种方式非常简洁,用IOUtils完成流的copy操作。
第二种方式是创建字节流,完成写入和写出操作。
代码如下:

package com.atguigu.java;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.UUID;

/**
 * @author cseroad
 */
public class Download extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 读取下载的文件内容
        String downloadFileName =  req.getParameter("filename");
        // 获取web应用的ServletContext对象
        ServletContext servletContext = getServletConfig().getServletContext();
        // 告诉客户端返回的数据类型
        String mimeType = servletContext.getMimeType("/file/" + downloadFileName);
        resp.setContentType(mimeType);
        resp.setCharacterEncoding("UTF-8");
        // 告诉客户端收到的数据用于下载使用
        String uuidFileName= UUID.randomUUID().toString();
        String new_filename = uuidFileName+".jpg";
        // 处理中文编码
        resp.setHeader("Content-Disposition","attachment;filename="+URLEncoder.encode(new_filename,"UTF-8"));
        // 获取响应的输出流
        ServletOutputStream outputStream = resp.getOutputStream();
        // 第二种方法:保存流中数据
        String  path = req.getServletContext().getRealPath("/file/");
        File file = new File(path,downloadFileName);
        if (!file.exists()) {
            req.setAttribute("message:", "您要下载的资源已被删除!!");
            return;
        }
        FileInputStream fileInputStream = new FileInputStream(file);
        byte[] buffer = new byte[1024];
        int len;
        while ((len=fileInputStream.read(buffer) )> 0){
            outputStream.write(buffer,0,len);
            outputStream.flush();
        }
        outputStream.close();
        fileInputStream.close();
    }
}
image.png

扩展

作为安全从业者,在以上代码处思考是否存在漏洞又该如何修复?
文件上传存在漏洞,属于任意文件上传,并未校验文件后缀名。
添加对文件后缀的强制校验,且采用白名单的方式

if (!(fileExtName.equals("png") || fileExtName.equals("gif") || fileExtName.equals("jpg"))){
    System.out.println("图片格式有误!只能是png、gif、jpg");
    return;
}
image.png

文件下载也存在漏洞,未对下载参数做任何过滤。

image.png
if (downloadFileName.contains("./") || downloadFileName.contains("../") || downloadFileName.contains("%")){
    System.out.println("文件名包含非法字符!");
    return;
}

通过判断参数是否存在./../%非法字符来过滤处理。

image.png

总结

学习了java实现文件上传和下载用到的一些类和方法,并进一步通过代码修复了自己写的漏洞。

你可能感兴趣的:(Java Web 文件上传和下载)