浅谈Jersey文件上传解析
字数 2061 2025-08-06 12:21:08

Jersey文件上传解析技术详解

0x01 Jersey文件上传的实现方式

1.1 添加multipart请求解析支持

在Jersey中解析multipart请求,需要使用jersey-media-multipart模块:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-multipart</artifactId>
</dependency>

启用multipart支持的配置方式:

在ResourceConfig类中配置:

import org.glassfish.jersey.media.multipart.MultiPartFeature;
import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("/api")
public class MyApplication extends ResourceConfig {
    public MyApplication() {
        register(MultiPartFeature.class);
    }
}

在web.xml中配置:

<init-param>
    <param-name>jersey.config.server.provider.classnames</param-name>
    <param-value>org.glassfish.jersey.media.multipart.MultiPartFeature</param-value>
</init-param>

1.2 实现方式

1.2.1 使用@FormDataParam注解

@POST
@Path("uploadimage")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public String uploadimage1(
    @FormDataParam("file") InputStream fileInputStream,
    @FormDataParam("file") FormDataContentDisposition disposition) {
    
    String imageName = Calendar.getInstance().getTimeInMillis() + disposition.getFileName();
    File file = new File(ARTICLE_IMAGES_PATH + imageName);
    
    try {
        FileUtils.copyInputStreamToFile(fileInputStream, file);
    } catch (IOException ex) {
        Logger.getLogger(UploadImageResource.class.getName()).log(Level.SEVERE, null, ex);
    }
    
    return "images/" + imageName;
}

1.2.2 使用MultiPart对象

@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(MultiPart multiPart) {
    for (BodyPart bodyPart : multiPart.getBodyParts()) {
        if (bodyPart instanceof FormDataBodyPart) {
            FormDataBodyPart filePart = (FormDataBodyPart) bodyPart;
            String fileName = filePart.getContentDisposition().getFileName();
            InputStream fileContent = filePart.getEntityAs(InputStream.class);
            // 处理文件内容
            fileContent.close();
        } else {
            // 处理其他表单字段
        }
    }
    return Response.ok("File uploaded successfully").build();
}

1.2.3 使用FormDataMultiPart对象

@POST
@Path("uploadimage2")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Viewable uploadimage2(FormDataMultiPart form, @Context HttpServletResponse response) throws UnsupportedEncodingException {
    FormDataBodyPart filePart = form.getField("file");
    FormDataBodyPart usernamePart = form.getField("username");
    
    InputStream fileInputStream = filePart.getValueAs(InputStream.class);
    FormDataContentDisposition formDataContentDisposition = filePart.getFormDataContentDisposition();
    String source = formDataContentDisposition.getFileName();
    String result = new String(source.getBytes("ISO8859-1"), "UTF-8");
    
    String filePath = ARTICLE_IMAGES_PATH + result;
    File file = new File(filePath);
    
    try {
        FileUtils.copyInputStreamToFile(fileInputStream, file);
    } catch (IOException ex) {
        Logger.getLogger(UploadImageResource.class.getName()).log(Level.SEVERE, null, ex);
    }
    
    response.setCharacterEncoding("UTF-8");
    Map map = new HashMap();
    map.put("src", result);
    return new Viewable("/showImg", map);
}

0x02 上传请求解析过程

2.1 解析请求过程

核心解析方法:org.glassfish.jersey.media.multipart.internal.MultiPartReaderClientSide#readMultiPart

  1. 根据请求的mediaType判断是否为multipart/form-data:

    • 是:创建FormDataMultiPart对象
    • 否:创建MultiPart对象
  2. 将请求的头信息(headers)复制到MultiPart对象的头信息(multiPartHeaders)中

  3. 如果是multipart/form-data请求,根据User-Agent判断是否需要进行文件名修复(fileNameFix)

  4. 遍历解析MIMEPart:

    • 为每个MIMEPart创建对应的BodyPart对象
    • multipart/form-data请求:创建FormDataBodyPart对象
    • 其他:创建普通BodyPart对象
    • 复制MIMEPart头信息到BodyPart对象
  5. 从BodyPart头信息中获取"Content-Type"并设置BodyPart的媒体类型(MediaType)

2.2 判断是否是Multipart请求

使用@Consumes({"multipart/*"})注解,只要Content-Type以multipart/开头的请求都会经过MultiPartReaderClientSide处理(注意:必须小写)

对于FormDataMultiPart解析,限制Content-Type必须为multipart/form-data(支持大小写转换)

0x03 获取上传的文件名

3.1 获取方式

方式一:通过FormDataContentDisposition

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public String upload(
    @FormDataParam("file") InputStream fis,
    @FormDataParam("file") FormDataContentDisposition fileDisposition) {
    
    String fileName = fileDisposition.getFileName();
    // ...
}

方式二:通过ContentDisposition参数

@POST
@Path("upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public String upload(FormDataMultiPart form, @Context HttpServletResponse response) throws UnsupportedEncodingException {
    FormDataBodyPart filePart = form.getField("file");
    FormDataContentDisposition formDataContentDisposition = filePart.getFormDataContentDisposition();
    Map<String, String> parameters = formDataContentDisposition.getParameters();
    String fileName = parameters.get("filename");
    // ...
}

3.2 文件名解析过程

  1. 文件名初始化在org.glassfish.jersey.media.multipart.ContentDisposition#createParameters中处理

  2. 通过defineFileName方法处理:

    • 优先处理filename*参数
    • 如果filename*为null,直接返回filename的值
    • 否则对filename*参数值进行处理:
      • 使用FILENAME_EXT_VALUE_PATTERN正则匹配
      • 通过isFilenameValueCharsEncoded方法检查编码
      • 提取charset、lang和filenameValueChars
      • 如果charset为UTF-8,拼接生成最终文件名
  3. 安全注意事项:

    • 不检查类似../的路径穿越符号
    • 可能导致跨目录文件写入
    • 利用场景:
      • 写入Linux定时任务(/etc/cron.d/)
      • 写入SSH公钥(需root权限)
      • 替换JDK系统jar文件

3.3 fileNameFix属性

作用:处理IE浏览器在filename参数中添加额外反斜杠的问题

触发条件

  1. 请求为formData类型
  2. User-Agent包含"MSIE"关键字

处理逻辑

  • 当fileNameFix为true时,对反斜杠内容进行截断处理
  • 去除文件名中的路径部分,只保留文件名

示例

// 正常情况:反斜杠会被剔除
filename="C:\\test.txt"  "C:test.txt"

// fileNameFix=true时:
filename="C:\\test.txt"  "test.txt"

0x04 与SpringMVC的对比

4.1 请求解析差异

  1. SpringMVC

    • 可能通过转换multipart请求绕过安全检查
    • 灵活支持多种请求方式间的解析转换
  2. Jersey

    • 严格区分请求类型
    • @FormParam不能处理multipart请求
    • 必须使用@FormDataParam处理multipart/form-data

4.2 参数绑定差异

Jersey示例

@POST
@Path("/test")
public Response test(@FormParam("msg") String msg) {
    return Response.ok().entity(msg).build();
}
  • @FormParam:仅处理application/x-www-form-urlencoded
  • @FormDataParam:专门处理multipart/form-data

安全最佳实践

  1. 文件名处理

    • 对上传文件名进行重命名
    • 检查并过滤路径穿越符号(../)
    • 限制文件扩展名
  2. 文件存储

    • 使用独立存储目录
    • 设置适当权限
    • 避免覆盖系统文件
  3. 请求验证

    • 严格验证Content-Type
    • 限制请求大小
    • 实施CSRF防护
  4. 错误处理

    • 自定义错误响应
    • 避免泄露服务器信息
    • 记录安全相关事件
Jersey文件上传解析技术详解 0x01 Jersey文件上传的实现方式 1.1 添加multipart请求解析支持 在Jersey中解析multipart请求,需要使用 jersey-media-multipart 模块: 启用multipart支持的配置方式: 在ResourceConfig类中配置: 在web.xml中配置: 1.2 实现方式 1.2.1 使用@FormDataParam注解 1.2.2 使用MultiPart对象 1.2.3 使用FormDataMultiPart对象 0x02 上传请求解析过程 2.1 解析请求过程 核心解析方法: org.glassfish.jersey.media.multipart.internal.MultiPartReaderClientSide#readMultiPart 根据请求的mediaType判断是否为multipart/form-data: 是:创建FormDataMultiPart对象 否:创建MultiPart对象 将请求的头信息(headers)复制到MultiPart对象的头信息(multiPartHeaders)中 如果是multipart/form-data请求,根据User-Agent判断是否需要进行文件名修复(fileNameFix) 遍历解析MIMEPart: 为每个MIMEPart创建对应的BodyPart对象 multipart/form-data请求:创建FormDataBodyPart对象 其他:创建普通BodyPart对象 复制MIMEPart头信息到BodyPart对象 从BodyPart头信息中获取"Content-Type"并设置BodyPart的媒体类型(MediaType) 2.2 判断是否是Multipart请求 使用 @Consumes({"multipart/*"}) 注解,只要Content-Type以 multipart/ 开头的请求都会经过 MultiPartReaderClientSide 处理(注意:必须小写) 对于FormDataMultiPart解析,限制Content-Type必须为 multipart/form-data (支持大小写转换) 0x03 获取上传的文件名 3.1 获取方式 方式一:通过FormDataContentDisposition 方式二:通过ContentDisposition参数 3.2 文件名解析过程 文件名初始化在 org.glassfish.jersey.media.multipart.ContentDisposition#createParameters 中处理 通过 defineFileName 方法处理: 优先处理 filename* 参数 如果 filename* 为null,直接返回 filename 的值 否则对 filename* 参数值进行处理: 使用 FILENAME_EXT_VALUE_PATTERN 正则匹配 通过 isFilenameValueCharsEncoded 方法检查编码 提取charset、lang和filenameValueChars 如果charset为UTF-8,拼接生成最终文件名 安全注意事项: 不检查类似 ../ 的路径穿越符号 可能导致跨目录文件写入 利用场景: 写入Linux定时任务(/etc/cron.d/) 写入SSH公钥(需root权限) 替换JDK系统jar文件 3.3 fileNameFix属性 作用 :处理IE浏览器在filename参数中添加额外反斜杠的问题 触发条件 : 请求为formData类型 User-Agent包含"MSIE"关键字 处理逻辑 : 当fileNameFix为true时,对反斜杠内容进行截断处理 去除文件名中的路径部分,只保留文件名 示例 : 0x04 与SpringMVC的对比 4.1 请求解析差异 SpringMVC : 可能通过转换multipart请求绕过安全检查 灵活支持多种请求方式间的解析转换 Jersey : 严格区分请求类型 @FormParam 不能处理multipart请求 必须使用 @FormDataParam 处理multipart/form-data 4.2 参数绑定差异 Jersey示例 : @FormParam :仅处理application/x-www-form-urlencoded @FormDataParam :专门处理multipart/form-data 安全最佳实践 文件名处理 : 对上传文件名进行重命名 检查并过滤路径穿越符号(../) 限制文件扩展名 文件存储 : 使用独立存储目录 设置适当权限 避免覆盖系统文件 请求验证 : 严格验证Content-Type 限制请求大小 实施CSRF防护 错误处理 : 自定义错误响应 避免泄露服务器信息 记录安全相关事件