[Spring Boot] 파일 업로드 & 다운로드 구현

2025. 7. 2. 10:54·Backend/Spring, Spring Boot

📌 단순 기능? 아니죠, 이것도 "설계"입니다.

Spring Boot에서 파일 업로드와 다운로드 기능은 흔하게 보이지만, 막상 제대로 설계하고 구현하려면 고려할 게 많습니다.
단순히 MultipartFile을 받는다고 끝이 아니며, 보안, 경로 설계, 예외처리, 응답 헤더 구성 등 세세한 이슈들이 얽혀 있습니다.


☑️ 파일 업로드: MultipartFile의 세계

Spring Boot에서는 업로드를 위해 클라이언트가 multipart/form-data 형식으로 요청을 보내야 합니다. 이를 컨트롤러에서 MultipartFile로 받아 처리합니다.

✅ 코드 예시: 업로드 컨트롤러

@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) {
        return ResponseEntity.badRequest().body("파일이 비어 있습니다.");
    }

    try {
        String uploadDir = "uploads/";
        String filePath = uploadDir + file.getOriginalFilename();

        File dest = new File(filePath);
        file.transferTo(dest);

        return ResponseEntity.ok("업로드 성공: " + file.getOriginalFilename());
    } catch (IOException e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                             .body("업로드 실패: " + e.getMessage());
    }
}

💡 설계 포인트

  • uploadDir은 application.yml 또는 application.properties에서 외부 설정으로 분리하는 것이 이상적입니다.
  • 중복 파일 처리는 UUID로 이름을 치환하거나, DB 연동을 통해 관리하는 방식이 일반적입니다.
  • 업로드 경로는 절대 static 리소스와 같은 공개 경로에 두지 마세요. 보안에 취약합니다.

☑️ 파일 다운로드: Content-Disposition의 마법

파일 다운로드는 단순한 링크 응답이 아닌, 명확한 헤더 구성과 파일 스트리밍 처리가 핵심입니다.

✅ 코드 예시: 다운로드 컨트롤러

@GetMapping("/download/{fileName:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
    try {
        Path filePath = Paths.get("uploads").resolve(fileName).normalize();
        Resource resource = new UrlResource(filePath.toUri());

        if (!resource.exists()) {
            return ResponseEntity.notFound().build();
        }

        String contentType = Files.probeContentType(filePath);
        contentType = contentType != null ? contentType : "application/octet-stream";

        return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType(contentType))
            .header(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=\"" + resource.getFilename() + "\"")
            .body(resource);
    } catch (IOException e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }
}

💡 설계 포인트

  • @PathVariable에서 정규식을 통해 확장자 포함을 명시 ({fileName:.+})해야 합니다.
  • Content-Disposition 헤더를 통해 다운로드를 강제할 수 있으며, UTF-8 파일명을 지원하려면 별도의 인코딩 처리 필요합니다.
  • 파일명이 노출되므로, **임의 경로 조작 방지 (.. 등)**를 필터링하거나 정규화(normalize) 필수입니다.

🔐 보안 고려 사항

항목설명
경로 탐색 공격 ../../ 같은 상대경로 접근 시 파일 유출 위험. normalize()나 화이트리스트 검증 필요
파일 크기 제한 spring.servlet.multipart.max-file-size 설정 필요
MIME 타입 검증 Files.probeContentType()과 화이트리스트 조합 사용
권한 관리 파일 업로드/다운로드는 인증된 사용자로 제한하고, 소유자 확인 로직 필수
 

📁 application.yml 설정 예시

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 15MB

🧠 마무리하며: 실무에서는 어떻게 쓰이는가?

실제 서비스에서는 업로드된 파일을 S3, FTP, 별도 정적 서버에 올리고, 메타 정보는 DB로 관리합니다.
Spring Boot 내에서만 처리하는 예시는 로컬 개발 단계나 간단한 내부 툴에서 주로 사용됩니다.

그리고 "단순 저장"이 아닌 "파일 시스템 설계"라는 관점에서 접근하면, 프로젝트의 품질이 확 달라집니다.
보안, 유지보수, 성능까지 고려한 구조가 결국 실무의 방향입니다.

저작자표시 (새창열림)

'Backend > Spring, Spring Boot' 카테고리의 다른 글

의존성 주입(Dependency Injection): 왜 필요한가?  (0) 2025.03.25
[SPRING] web.xml 파일 cvc-id.3 관련 에러 해결  (0) 2022.03.18
[Spring] STS (Spring Tool Suite) 란?  (0) 2022.02.07
[Spring] 한글 깨짐 방지를 위해 web.xml UTF-8 설정  (0) 2022.02.04
[Spring] 스프링의 주요 모듈 목록  (0) 2022.01.25
'Backend/Spring, Spring Boot' 카테고리의 다른 글
  • 의존성 주입(Dependency Injection): 왜 필요한가?
  • [SPRING] web.xml 파일 cvc-id.3 관련 에러 해결
  • [Spring] STS (Spring Tool Suite) 란?
  • [Spring] 한글 깨짐 방지를 위해 web.xml UTF-8 설정
taetae_
taetae_
기록하기를 좋아하라, 쉬지 말고 기록해라, 생각이 떠오르면 수시로 기록하라, 기억은 흐려지고 생각은 사라진다. 머리를 믿지 말고 손을 믿어라.
  • taetae_
    태태의 개발 일지
    taetae_
  • 전체
    오늘
    어제
    • 분류 전체보기 (165)
      • Front (29)
        • HTML, CSS (14)
        • JSP (6)
        • JavaScript (9)
        • React, Vue (0)
      • Backend (58)
        • Java, Kotlin (39)
        • JPA, QueryDSL, ORM (1)
        • Spring, Spring Boot (8)
        • Database (10)
      • 인프라, DevOps (6)
        • AWS, Cloud (1)
        • Docker, 배포 (3)
        • Git, 협업도구 (2)
      • 알고리즘, 코딩테스트 (34)
        • 백준 (24)
        • 프로그래머스 (9)
      • CS 기초 (7)
        • 자료구조, 알고리즘 이론 (0)
        • 운영체제 (2)
        • 네트워크 (5)
      • 개인 일반 (20)
        • 개발 도구, IDE (13)
        • 코드 품질, 리팩토링 (0)
        • 회고, 학습 정리 (2)
      • 시리즈 (4)
        • 대규모 트래픽 공연 티켓팅 시스템 (4)
      • 기타 (6)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Backend
    Thymeleaf
    kafka
    자바
    Python AI 개발 환경
    오라클
    #Docker #Container #Linux #Kernel #Namespace #Cgroups #DevOps #Virtualization
    gemini cli 사용법
    Windows AI 도구
    프로그래머스
    CDN이란
    분산락
    #vscode #intellij #ide비교 #개발툴 #개발자팁 #단축키정리 #생산성향상 #프로그래밍툴 #개발자블로그 #개발자성장 #코딩효율화 #리팩토링 #디버깅팁 #springboot개발 #프론트엔드개발 #백엔드개발 #개발환경 #코딩툴추천
    Redlock
    springgateway
    대규모트래픽
    tranactional
    Java
    spring
    #dns #도메인네임시스템 #dns란 #웹기초지식 #웹개발자팁 #개발자블로그 #백엔드개발 #프론트엔드개발 #웹성능최적화 #seo최적화 #dns작동원리 #dns서버 #dns보안 #dnsoverhttps #dns최적화 #ttl #dig #nslookup #cdn #도메인과ip #기술블로그 #개발자공부 #코딩블로그
    outbox
    Google Gemini API 키
    git
    Gemini Python 설치
    백준
    Gemini CLI 설치
    MSA
    gemini-pro 모델 사용법
    redis
    #스프링부트파일업로드 #스프링파일다운로드 #spring파일업로드예제 #java파일업로드다운로드 #springboot예제코드
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
taetae_
[Spring Boot] 파일 업로드 & 다운로드 구현
상단으로

티스토리툴바