JustDoEat

MultipartFile 에서 파일 확장자를 검사 해보자. + 멀티파트에서 @Validated가 안되는 현상 본문

Yajoba/Backend

MultipartFile 에서 파일 확장자를 검사 해보자. + 멀티파트에서 @Validated가 안되는 현상

kingmusung 2024. 11. 28. 02:00

개요

제이슨 형식의 본문과, 이미지 파일을 같이 받아야 하는 상황이 생겼다.

 

여기서 문제는 application/json과 multipart/form-data 형식을 같이 받아야 하는데 application/json 같은 경우는

@Validated 어노테이션을 붙임으로써 클라이언트가 요청을 잘못 보내면 DTO선에서 컷 해버린다.

 

(DTO는 데이터를 옮기는 책임만 치는데 사실 컨트롤러단에서 요청에 대한 유효성을 DTO에서 검증을 하는 게 말이 되는 건가 싶다..

그냥 값을 받고, 엔티티 객체 생성시점에 입력을 검증하는 방식으로 바꿔야 하나 고민 중이다.)

여하튼 RequestDto에 한에서만 검증의 책임도 같이 주었다..

 

PNG, JPG, JPEG의 확장자만 와야 하는데. 만약 이상한 파일을 보냈다면? 일반적인 @Validated로 검증이 불가능하다.

 

"확장자를 어떻게 검증을 해야 할까"

 


 

1. 일단 파일을 받고, 따로 유효성 검증 클래스를 만들어서 파일의 확장자를 검증한다.

 

2. 받음과 통시에 검사를 먼저 한다.

 

상대적으로 쉬운 길은 1번이었다, 2번의 경우도 List <MultipartFile>로 오는 게 불편해서, ModelAttribute를 사용해서 데이터를 전달받는 순간 DTO로 받기 위해 사용했다(RequestPart의 경우 복잡한 변환 로직은 이해하지 못함)

 

이러한 과정이 있었기에 때려 죽어도 받음과 동시에 검사를 먼저 하고 싶었다.

 

여러 삽질 끝에 아래 글을 보고 힌트를 얻었다.

 

Spring: validation with @RequestPart isn't working

If I send a PNG file, the file name is returned with a HTTP 200 code. While only a PDF file should be accepted : @PutMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity<

stackoverflow.com

 

어노테이션을 내가 만들어서 유효성 검사를 해보는 것이다!

 


어노테이션 정의

 

@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ProductImageCollectionValidator.class)
public @interface ValidProductImages {
    String message() default "올바른 이미지 파일이 아닙니다.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

 

@Target 은 어노테이션이 적용이 될 범위를 나타내준다, 메서드의 인자, 생성자, 클래스, 필드변수 등 어노테이션을 적용하는 대상 범위에 따라 Target은 바꾸는 게 좋겠다.

 

@Retention은 런타임 중에 계속에서 요청이 들어오고, 요청 때마다 어노테이션이 필요하니 RUNTIME으로 했다.

 

@Constraint는 어노테이션 동작의 핵심이 되는 구현채 정도라고 생각하면 된다.


검증기 구현

 

먼저 검증기는

 

 

해당 인터페이스를 구현을 해야 한다.

 

A는 내가 만든 어노테이션,

T에유효성 검증을 하고자 하는 클래스를 넣어주면 된다

(모든 클래스를 받고 싶다면 Object로 넣어주고 나중에 제약조건을 걸어줘도 된다.)

 

오버라이딩 해야 하는 메서드는 두 개밖에 없다,

initialize 말 그대로 초기화 작업을 하기 위한 메서드

isValid는 검증을 하기 위한 로직이 들어있는 메서드 이다.

 

ConstraintValidator<ValidProductImages, ProductImageCollection>

 

나의 경우는 ProductImageCollection 클래스에 대해서만 검증이 필요해서 위와같이 적어주었다.

 


ContentType만으로는 파일의 확장자를 알 수 없다.

 

 

보통 우리가 요청을 보낼 때 Content-Type을 사용하는데, HTTP 헤더 중 하나로, 전송되는 데이터의 형식을 나타낸다.

 

서버나 클라이언트가 데이터를 주고받을 때 이 헤더를 사용하여 서로 데이터의 종류와 형식을 알 수 있도록 한다.

 

"야 이건 000 Type 이니까 타입에 맞게 처리해!!"라고 알려주는 헤더이다.

 

 

 

getContentType()으로 가지고 오면,  multipart/form-data 형식이 뜨게 된다.

 

 

당연히 내가 요청을 보낼 때 multipart/form-data안에 png, jpeg를 넣어서 보냈기 때문이다.

 

내가 궁금한 건 multipart/form-data가 아닌. png, jpeg 등 이미지 파일이다.

 


Version. 1 파일의 이름을 토대로 확장자를 검출

        @Override
        public boolean isValid(ProductImageCollection productImageCollection, ConstraintValidatorContext context) {
            if (productImageCollection == null || productImageCollection.isEmpty()) {
                return false;
            }
            List<MultipartFile> images = productImageCollection.getProductImages();

            for (MultipartFile image : images) {
                String filename = image.getOriginalFilename();
                if (filename == null || (!filename.endsWith(".png") && !filename.endsWith(".jpg") && !filename.endsWith(".jpeg"))) {
                    return false;
                }

            return true;
        }
    }

 

. getOriginalFileName()으로 파일이름을 가지고 와서 끝 문자가. png. jpeg. jpg로  끝나는지 확인을 해준다.

 

이렇게만 해도 사실 돌아간다.

 

하지만, 확장자를 위장한 파일 즉 png인척 하는 text 파일이라면 심각한 에러를 발생시킬 수 있을 것이다.


Version. 2 Apache Tika를 이용한 검사.

 

출처: https://en.wikipedia.org/wiki/Apache_Tika

 

Tika는 확장자와 관계없이. 파일의 실제 콘텐츠를 확인하여 파일의 포맷을 인식하는 기능을 가지고 있다. 그 외에도 메타데이터 추출, 텍스트 추출등의 기능이 있다.

 


 실제 적용