π€ λ¬Έμ μν©
Spring Bootλ‘ λ§λ μμ νμΌ μ λ‘λ κΈ°λ₯μμ μ΄μν νμμ΄ λ°μνμ΅λλ€.
- A μ»΄ν¨ν°: κ°μ μμ νμΌ μ λ‘λ β β μ±κ³΅
- B μ»΄ν¨ν°: κ°μ μμ νμΌ μ λ‘λ β β "μ¬λ°λ₯Έ μμ νμΌ νμμ΄ μλλλ€." μ€λ₯
λ μ»΄ν¨ν° λͺ¨λ:
- λμΌν Windows νκ²½
- λμΌν Chrome λΈλΌμ°μ
- λμΌν
.xlsxνμΌ
λ체 μ?! π€―
π μμΈ λΆμ
λ²μΈμ λ°λ‘... νμ»΄μ€νΌμ€!
B μ»΄ν¨ν°μλ νμ»΄μ€νΌμ€(νμ
)κ° μ€μΉλμ΄ μμκ³ , .xlsx νμΌ μ°κ²° νλ‘κ·Έλ¨μ΄ νμ
λ‘ μ€μ λμ΄ μμμ΅λλ€.
λ‘κ·Έλ₯Ό νμΈν΄λ³΄λ:
- A μ»΄ν¨ν°:
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheetβ - B μ»΄ν¨ν°:
Content-Type: application/haansoftxlsxβ
μ μ΄λ° μΌμ΄?
νμ»΄μ€νΌμ€ μ€μΉ μ Windows λ μ§μ€νΈλ¦¬κ° λ³κ²½λ¨
- νμΌ νμ₯μλ³ MIME νμ λ§€νμ΄ νμ»΄ μ μ©μΌλ‘ λ³κ²½
.xlsxβapplication/haansoftxlsx(νμ»΄ μ μ© MIME νμ )
λΈλΌμ°μ λ μ΄μ체μ μ MIME νμ μ λ°λ¦
- Chromeμ΄ νμΌμ μ½μ λ OSμ λ μ§μ€νΈλ¦¬ μ 보λ₯Ό μ°Έμ‘°
- νμ»΄μ€νΌμ€κ° μ€μΉλλ©΄ μλμΌλ‘ νμ»΄ MIME νμ μ¬μ©
μλ²λ νμ€ MIME νμ λ§ νμ©νλλ‘ κ²μ¦
- νμ»΄ μ μ© MIME νμ μ κ²μ¦ ν΅κ³Ό μ€ν¨ β μ€λ₯ λ°μ
Windows λ μ§μ€νΈλ¦¬
βββ HKEY_CLASSES_ROOT
βββ .xlsx
βββ Content Type: "application/haansoftxlsx" // νμ»΄μ€νΌμ€κ° λ³κ²½!π‘ ν΄κ²° λ°©λ²
1. μλ² μΈ‘ ν΄κ²° (κΆμ₯ β¨)
κΈ°μ‘΄ μ½λ (λ¬Έμ λ°μ):
// MIME νμ
κ²μ¦
String contentType = file.getContentType();
if (contentType != null &&
!contentType.equals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") &&
!contentType.equals("application/vnd.ms-excel") &&
!contentType.equals("application/octet-stream")) {
result.setCode(PROGRAM_CODE.RESULT_FAIL);
result.setMsg("μ¬λ°λ₯Έ μμ
νμΌ νμμ΄ μλλλ€.");
return result;
}
κ°μ λ μ½λ:
// MIME νμ
κ²μ¦ (λ€μν νκ²½ μ§μ)
String contentType = file.getContentType();
if (contentType != null &&
!contentType.equals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") && // νμ€ .xlsx
!contentType.equals("application/vnd.ms-excel") && // νμ€ .xls
!contentType.equals("application/octet-stream") && // μΌλ° λ°μ΄λ리
!contentType.equals("application/haansoftxlsx") && // νμ»΄μ€νΌμ€ νμ
β
!contentType.equals("application/x-tika-msoffice") && // Apache Tika
!contentType.equals("application/zip")) { // μΌλΆ λΈλΌμ°μ μμ xlsxλ₯Ό zipμΌλ‘ μΈμ
result.setCode(PROGRAM_CODE.RESULT_FAIL);
result.setMsg("μ¬λ°λ₯Έ μμ
νμΌ νμμ΄ μλλλ€. (Content-Type: " + contentType + ")");
return result;
}
// νμΌ νμ₯μλ‘ μΆκ° κ²μ¦ (λ μμ μ )
String originalFilename = file.getOriginalFilename();
if (originalFilename != null) {
String lowerFilename = originalFilename.toLowerCase();
if (!lowerFilename.endsWith(".xlsx") && !lowerFilename.endsWith(".xls")) {
result.setCode(PROGRAM_CODE.RESULT_FAIL);
result.setMsg("μμ
νμΌλ§ μ
λ‘λ κ°λ₯ν©λλ€. (.xlsx, .xls)");
return result;
}
}
2. ν΄λΌμ΄μΈνΈ μΈ‘ ν΄κ²° (μμλ°©νΈ)
μ¬μ©μκ° μ§μ νμΌ μ°κ²° νλ‘κ·Έλ¨μ λ³κ²½:
.xlsxνμΌ μ°ν΄λ¦ β μμ±- μ°κ²° νλ‘κ·Έλ¨ β λ³κ²½
- Microsoft Excel μ ν
- νμΈ
λ¨μ : λͺ¨λ μ¬μ©μμκ² μꡬν μ μμ (λΉνμ€μ )
π λ€μν νκ²½μμμ MIME νμ
| νκ²½ | MIME Type |
|---|---|
| Microsoft Excel (νμ€) | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
| MS Excel (.xls) | application/vnd.ms-excel |
| νμ»΄μ€νΌμ€ νμ | application/haansoftxlsx β |
| WPS Office | application/wps-office.xlsx |
| μΌλΆ λΈλΌμ°μ (zip μΈμ) | application/zip |
| μΌλ° λ°μ΄λ리 | application/octet-stream |
| Apache Tika | application/x-tika-msoffice |
π― Best Practice
1. MIME νμ λ§μΌλ‘ κ²μ¦νμ§ λ§μΈμ
// β λμ μ: MIME νμ
λ§ κ²μ¦
if (!contentType.equals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) {
throw new InvalidFileException();
}
// β
μ’μ μ: MIME νμ
+ νμΌ νμ₯μ + μ€μ λ΄μ© κ²μ¦
if (isInvalidMimeType(contentType)) {
throw new InvalidFileException();
}
if (!filename.endsWith(".xlsx") && !filename.endsWith(".xls")) {
throw new InvalidFileException();
}
// μ€μ λ‘ μμ
νμΌμΈμ§ Apache POIλ‘ κ²μ¦
try (Workbook workbook = WorkbookFactory.create(file.getInputStream())) {
// μ μμ μΌλ‘ μ΄λ¦¬λ©΄ μ€μ μμ
νμΌ
}
2. νμ© λ¦¬μ€νΈ λ°©μλ³΄λ€ μ°¨λ¨ λ¦¬μ€νΈ λ°©μ κ³ λ €
// Option 1: νμ© λ¦¬μ€νΈ (νμ¬ λ°©μ)
if (!isAllowedMimeType(contentType)) {
throw new InvalidFileException();
}
// Option 2: μ°¨λ¨ λ¦¬μ€νΈ + νμΌ λ΄μ© κ²μ¦ (λ μ μ°)
if (isDangerousMimeType(contentType)) { // exe, sh, bat λ±λ§ μ°¨λ¨
throw new InvalidFileException();
}
// Apache POIλ‘ μ€μ μμ
νμΌμΈμ§ κ²μ¦
validateExcelContent(file);
3. λ‘κΉ μΆκ°λ‘ λλ²κΉ μ½κ²
log.info("νμΌ μ
λ‘λ μλ - νμΌλͺ
: {}, Content-Type: {}, ν¬κΈ°: {} bytes",
filename, contentType, file.getSize());
π μΆκ° κ³ λ €μ¬ν
보μμ μν μ€μ νμΌ λ΄μ© κ²μ¦
MIME νμ κ³Ό νμ₯μλ μ½κ² μ‘°μ κ°λ₯ν©λλ€. μ§μ§ 보μμ΄ νμνλ€λ©΄:
// Apache POIλ‘ μ€μ μμ
νμΌμΈμ§ κ²μ¦
try (InputStream is = file.getInputStream();
Workbook workbook = WorkbookFactory.create(is)) {
// μ μμ μΌλ‘ Workbookμ΄ μμ±λλ©΄ μ€μ μμ
νμΌ
log.info("μμ
νμΌ κ²μ¦ μ±κ³΅: {} μνΈ λ°κ²¬", workbook.getNumberOfSheets());
} catch (Exception e) {
throw new InvalidFileException("μμ
νμΌμ΄ μμλμκ±°λ μ¬λ°λ₯Έ νμμ΄ μλλλ€.");
}
μμ‘΄μ± μΆκ°
<!-- pom.xml -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
π μ 리
- λΈλΌμ°μ μ Content-Typeμ OSμ νμΌ μ°κ²° νλ‘κ·Έλ¨μ λ°λΌ λ¬λΌμ§λλ€
- νμ»΄μ€νΌμ€ μ€μΉλ§μΌλ‘λ MIME νμ μ΄ λ³κ²½λ©λλ€
- μλ²λ λ€μν νκ²½μ MIME νμ μ νμ©ν΄μΌ ν©λλ€
- MIME νμ κ²μ¦ + νμ₯μ κ²μ¦ + μ€μ λ΄μ© κ²μ¦μ ν¨κ» μ¬μ©νμΈμ
κ°μ νμΌ, κ°μ λΈλΌμ°μ μΈλ° λ€λ₯Έ κ²°κ³Όκ° λμ¨λ€λ©΄... μ€μΉλ νλ‘κ·Έλ¨μ μμ¬ν΄λ³΄μΈμ! π΅οΈ
κ΄λ ¨ ν€μλ: Spring Boot, νμΌ μ λ‘λ, MIME Type, Content-Type, νμ»΄μ€νΌμ€, Hancom Office, Excel, MultipartFile, νμΌ κ²μ¦
μ°Έκ³ μλ£:
'κ°λ° μΌμ§ π©βπ»' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
| AES-256 μνΈν κ°μ΄λ - Spring boot (0) | 2025.11.11 |
|---|---|
| λ§₯λΆμμ TSC νλ¦°ν° λλΌμ΄λ² μ€μΉνλ λ°©λ² (0) | 2025.11.11 |
| λ§₯λΆμ java 21 μ€μΉ λ°©λ² (0) | 2025.10.23 |
| package.json vs package-lock.json (0) | 2025.10.21 |
| λΈλΌμ°μ κ° λ©μ·λ€: DOM Node λ©λͺ¨λ¦¬ λμ ν΄κ²°κΈ° (0) | 2025.10.16 |