Spring

[Spring] ajax를 이용해 파일 업로드 시 문제점 보완

jane.dev 2021. 10. 13. 08:59
반응형

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());
        }
    }
}