Secure File Upload in Spring Boot with Metadata Removal



In this article, we'll explore how to implement a secure file upload feature in a Spring Boot application that allows uploading files, validates them, saves metadata temporarily, and removes the metadata after processing to ensure security.


Steps Overview

  1. Upload the file.
  2. Validate file (size, type, etc.).
  3. Save metadata temporarily.
  4. Securely remove metadata after processing.

1. Project Setup

Dependencies (pom.xml)

Ensure you have the following dependencies in your Spring Boot project:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
</dependencies>

2. File Metadata Entity

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
public class FileMetadata {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String filename;
    private String fileType;
    private long fileSize;
    private LocalDateTime uploadTime;

    public FileMetadata() {}

    public FileMetadata(String filename, String fileType, long fileSize, LocalDateTime uploadTime) {
        this.filename = filename;
        this.fileType = fileType;
        this.fileSize = fileSize;
        this.uploadTime = uploadTime;
    }
}

3. Repository

import org.springframework.data.jpa.repository.JpaRepository;

public interface FileMetadataRepository extends JpaRepository<FileMetadata, Long> {
}

4. File Upload Service

import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;

@Service
public class FileUploadService {
    private final FileMetadataRepository metadataRepository;
    private final String UPLOAD_DIR = "uploads/";

    public FileUploadService(FileMetadataRepository metadataRepository) {
        this.metadataRepository = metadataRepository;
    }

    public String uploadFile(MultipartFile file) throws IOException {
        validateFile(file);
        String filename = System.currentTimeMillis() + "_" + file.getOriginalFilename();
        Path uploadPath = Path.of(UPLOAD_DIR, filename);
        Files.copy(file.getInputStream(), uploadPath, StandardCopyOption.REPLACE_EXISTING);

        FileMetadata metadata = new FileMetadata(filename, file.getContentType(), file.getSize(), LocalDateTime.now());
        metadataRepository.save(metadata);

        removeMetadata(metadata.getId());

        return "File uploaded successfully: " + filename;
    }

    private void validateFile(MultipartFile file) {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        long maxFileSize = 5 * 1024 * 1024;

        if (file.isEmpty()) {
            throw new RuntimeException("File is empty.");
        }
        if (file.getSize() > maxFileSize) {
            throw new RuntimeException("File size exceeds limit.");
        }
        if (!extension.matches("jpg|png|pdf|txt")) {
            throw new RuntimeException("Invalid file type.");
        }
    }

    private void removeMetadata(Long id) {
        new Thread(() -> {
            try {
                Thread.sleep(5000);
                metadataRepository.deleteById(id);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
    }
}

5. File Upload Controller

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

@RestController
@RequestMapping("/api/files")
public class FileUploadController {
    private final FileUploadService fileUploadService;

    public FileUploadController(FileUploadService fileUploadService) {
        this.fileUploadService = fileUploadService;
    }

    @PostMapping("/upload")
    public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
        try {
            String response = fileUploadService.uploadFile(file);
            return ResponseEntity.ok(response);
        } catch (IOException e) {
            return ResponseEntity.badRequest().body("File upload failed: " + e.getMessage());
        }
    }
}

6. Application Configuration

application.properties:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=5MB


Previous Post Next Post