본문 바로가기
Frameworks/SPRING

TIKA를 이용한 서버측 파일 확장자 체크

by 달팽이 "@... 2022. 3. 2.

 많은 레퍼런스들을 참고하여 고민하며 구현한 코드지만 저 스스로도 취업준비생인 초보 개발자이기 때문에 코드 상의 문제 혹은 더 바람직한 우수사례들이 있을 수 있습니다.  코드 작성 시 참고 정도로 활용해주시고 문제나 개선 가능한 부분 발견 시 공유해주신다면 감사하겠습니다.

 

파일명의 String에서 확장자명을 추출하는 방식으로 타입 체크 시에는 위변조 위험에 쉽게 노출되기 때문에 Apache의 Tika를 이용해보겠습니다

 

Exception

public class NotImageFileException extends RuntimeException {

    public NotImageFileException() {

        super();

    }

}

 

이미지 파일이 아닐 시 발생될 Custom Exception 입니다

 

 

UploadFileUtils

public class UploadFileUtils {

    public static List<String> uploadFile(MultipartHttpServletRequest mtfRequest) throws IOException, NotImageFileException {

        // multipartFile 리스트
        List<MultipartFile> mtfs = mtfRequest.getFiles("image");

        List<String> fileList = new ArrayList<>();

        for (MultipartFile mtf : mtfs) {

            if (!detectFileType(mtf)) {
                throw new NotImageFileException();
            }

            // 파일 이름
            String originalFileName = mtf.getOriginalFilename();

            // 공백문자를 언더스코어로 교체하기
            originalFileName = originalFileName.replace(' ', '_');

            // 업로드 경로
            String uploadPath = mtfRequest.getSession().getServletContext().getRealPath(File.separator + "WEB-INF" + File.separator);

            // 조회시 과부하를 막기 위한 경로 구분용 현재 날짜를 yyyyMMdd 형태로 반환받기
            String date = getTodayDate();

            // 파일 이름 중복문제를 해결하기 위한 UUID
            String uuid = UUID.randomUUID().toString();

            // 데이터베이스에 기록될 경로 형태
            // 현재날짜 + UUID + "_" + 파일
            String dbFile = File.separator + "uploadedImages" + File.separator + date + uuid + "_" + originalFileName;


            // 위에서 생성했던 리스트에 첨부 이미지 데이터 담기
            fileList.add(dbFile);

            // 파일 저장 경로
            String saveFile = uploadPath + dbFile;

            // 파일 경로에 저장하기
            mtf.transferTo(new File(saveFile));

        }

        return fileList;  // 파일 리스트 반환

    }
}

 

이전에 구현해 사용중이던 파일 업로드 관련 클래스 입니다

 

 

해당 코드 부분에서 detectFileType 멤버 함수가 false 반환 시 NotImageFileException을 발생 시킵니다 

if (!detectFileType(mtf)) {
    throw new NotImageFileException();
}

 

 

detectFileType

private static boolean detectFileType(MultipartFile mtf) throws IOException {
    Tika tika = new Tika();

    InputStream fileInputStream = mtf.getInputStream();

    String mimeType = tika.detect(fileInputStream);

    return mimeType.startsWith("image");
}

 

해당 멤버 변수에서는 tika.detect 메소드로 file의 mime type을 String으로 반환받아 image인지 확인하여

Boolean 값을 반환 합니다  

 

ImageController

@RestController
@RequestMapping("/file/")
public class ImageController {

    @PostMapping(value="/upload")
    public ResponseEntity<List<String>> uploadFile(MultipartHttpServletRequest mtfRequest) throws Exception {

        // 파일(들)을 지정된 경로에 저장 및 경로 리스트 반환
        try {
            List<String> attachedImgs = UploadFileUtils.uploadFile(mtfRequest);

            // Image DTO 리스트와 함께 OK status response code 반환
            return new ResponseEntity<>(attachedImgs, HttpStatus.OK);

        } catch(NotImageFileException e) {

            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

        }

    }

}

 

컨트롤러에서는 NotImageException 발생 시 클라이언트 측으로 400 status code를 반환 합니다