[Spring] ajax를 이용해 파일 업로드 시 문제점 보완
2021.10.12 - [Spring] - [Spring] ajax를 이용한 파일 업로드
[Spring] ajax를 이용한 파일 업로드
Ajax를 이용해 파일 데이터만을 전송하는 방법 2021.10.11 - [Spring] - [Spring] form 태그를 이용한 파일 업로드 [Spring] form 태그를 이용한 파일 업로드 form 태그로 파일을 업로드하는 방식은 페이지 이동.
wheneveryouwantsz.tistory.com
위처럼 파일 업로드 진행시 지정한 경로에 파일이 업로드되면
1. 이미지 파일외에도 여러 확장자의 파일 업로드 가능 → 첨부파일을 통한 웹 공격이 가능함
2. 동일한 파일명일 경우 기존의 파일이 없어지고 나중에 업로드된 파일로 교체됨
3. 업로드된 파일의 용량이 큰 경우 썸네일을 생성해야 함
위 문제들을 보완하기 위해 먼저
1. 파일 확장자 제한
확장자가 exe, sh, zip, alz 등의 경우와 특정 크기 이상의 파일은 업로드를 할 수 없도록 제한을 자바스크립트로 처리
$(document).ready(function(){
// exe, sh, zip, alz를 제한하는 정규표현식
var regex = new RegExp("(.*?)\.(exe|sh|zip|alz)$");
// 5MB
var maxSize = 5242880
// 첨부파일 업로드시 checkExtension()을 호출
function checkExtension(fileName, fileSize){
// 파일 사이즈 검사
if(fileSize >= maxSize){
alert("파일 사이즈가 초과되었습니다.(5MB 미만)");
return false;
}
// 파일 확장자 검사
// .test(): 대응되는 문자열이 있는지 검사하는 메서드
if(regex.test(fileName)){
alert("해당 종류의 파일은 업로드 할 수 없습니다.(exe/sh/zip/alz 제외)");
return false;
}
return true;
}
}
정규표현식 참고
2021.10.14 - [JavaScript] - [JavaScript] 정규표현식
[JavaScript] 정규표현식
정규 표현식 문자열에 나타나는 특정 문자 조합과 매칭시키기 위해 사용하는 패턴 정규 표현식을 만드는 방법 1. 정규식 리터럴 사용 var regex = /a+b/; 슬래시로 감싸는 패턴으로 스크립트를 불러
wheneveryouwantsz.tistory.com
2. 중복된 이름의 파일명 처리와 한 폴더내에 너무 많은 파일이 저장되지 않도록 함
중복된 파일명처리는 UUID(범용 고유 식별자)를 추가해 중복이 발생할 가능성이 거의 없도록 하고
@ResponseBody
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile) {
@ResponseBody
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile) {
log.info("ajax post update!");
String uploadFolder = "C:\\upload_data\\test";
for(MultipartFile multipartFile : uploadFile) {
String uploadFileName = multipartFile.getOriginalFilename();
// UUID 발급해 유일 식별자 생성
UUID uuid = uuid.toString() + "_" + uploadFilName;
// uuid를 문자로 변경하고 _로 구분하고 기존의 파일명 앞에 붙여줌
uploadFileName = uuid.toString() + "_" + uploadFileName;
File saveFile = new File(uploadFolder, uploadFileName);
try {
multipartFile.transferTo(saveFile);
}catch(Exception e) {
log.error(e.getMessage());
}
}
}
날짜 별로 폴더를 만들어 업로드 되는 파일이 해당 날짜 폴더에 저장될 수 있도록 함
private String getFolder() {
// 날짜를 원하는 포맷으로 출력하는 SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = new Date();
// 위에서 선언한 Date 객체를 String으로 format 지정
String str = sdf.format(date);
// 파일 구분자는 운영체제마다 다른데,
// File.separator는 JVM이 실행되는 환경의 OS의 구분자를 제공해주는 API
return str.replace("-", File.separator);
}
getFolder() 메서드를 생성해 2021년 10월 13일의 날짜에 업로드 되는 파일은
'2021-10-13'의 포맷으로 출력된 날짜를 -(하이픈)을 파일구분자를 통해 나눠 2021 폴더 아래 10 폴더 아래 13 폴더 내부에 저장
@ResponseBody
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile) {
String uploadFolder = "C:\\upload_data\\test";
// File 객체를 생성해 저장될 경로와 파일 이름을 넣어줄 때, getFolder() 메서드 호출
File uploadPath = new File(uploadFolder, getFolder());
// 위에서 폴더 지정하는 형식을 받아와 exists() 메서드로 해당 폴더가 존재하지 않는것을 확인하여
// mkdirs() 메서드로 생성
if(uploadPath.exists() == false) {
uploadPath.mkdirs();
}
// 기존 로직 수정
// File 객체를 생성해 저장될 경로와 파일이름을 넣어줌
//File saveFile = new File(uploadFolder, uploadFileName);
// uploadFolder에서 'C:\\upload_data\\test' 경로만 지정했는데 날짜 정보를 포함한 폴더로 변경
File saveFile = new File(uploadPath, uploadFileName);
}
3. 업로드된 파일보다 용량이 작은 썸네일을 통해 미리보기로 띄워줌
maven repository에서 Thumbnailator를 pom.xml에 추가
썸네일을 생성하기에 앞서 checkImageType() 메서드를 생성해 이미지 파일 여부를 체크
private boolean checkImageType(File file) {
try {
/* Files.probeContentType(): 파일의 mime 타입을 확인하는 메서드
* 확장자를 이용해 mime 타입을 확인하는데, 확장자가 없으면 null을 반환
* toPath(): 파일의 경로를 가져옴
*/
String contentType = Files.probeContentType(file.toPath());
log.info("contentType: " + contentType);
// startsWith(): 대상 문자열이 파라미터 값의 문자열로 시작하는지 확인하는 메서드 - 시작하면 true 반환
return contentType.startsWith("image");
}catch(IOException e) {
e.printStackTrace();
}
return false;
}
}
이미지 파일이 맞다면 썸네일을 생성
@ResponseBody
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile) {
@ResponseBody
@PostMapping("/uploadAjaxAction")
public void uploadAjaxPost(MultipartFile[] uploadFile) {
log.info("ajax post update!");
String uploadFolder = "C:\\upload_data\\test";
for(MultipartFile multipartFile : uploadFile) {
String uploadFileName = multipartFile.getOriginalFilename();
UUID uuid = uuid.toString() + "_" + uploadFilName;
uploadFileName = uuid.toString() + "_" + uploadFileName;
try {
// File 객체 생성하는 부분을 try~catch문 내부로 이동
File saveFile = new File(uploadFolder, uploadFileName);
multipartFile.transferTo(saveFile);
// checkImageType()를 호출해 저장될 파일이 이미지 파일이 맞는지 체크
if(checkImageType(saveFile)){
// FileOutputStream 객체(파일을 생성)에 생성자를 File타입으로 생성
// 해당 로직으로 생성된 파일은 "s_UUID_기존파일명"
FileOutputStream thumbnail = new FileOutputStream(
new File(uploadPath, "s_" + uploadFileName));
/* Thumbnailator 의 파일생성
* .createThumbnail()의 파라미터에는 InputStream, File 객체, width, height을 넣어줌
*/
Thumbnailator.createThumbnail(multipartFile.getInputStream(), thumnail, 100, 100);
/* 사용한 파일 객체를 닫아줌, 자바는 프로그램이 종료될 때 자동으로 파일을 닫아주지만
* 사용한 파일은 직접 닫아 다시 사용할 때 에러가 발생하지 않도록 함
*/
thumbnail.close();
}
}catch(Exception e) {
log.error(e.getMessage());
}
}
}