본문 바로가기
Spring

Object Storage 이미지 출력

by yang sing 2023. 6. 9.

안녕하세요. 

이번 포스팅에서는 요즘 프로젝트에서 안쓸 수 없는 Object Storage에서 이미지에 접근하는 방법을 포스팅 하려고 합니다.

 

회사에서 지난 프로젝트의 경우 Cloud 환경의 NAS를 이용하여 각 서버에 마운트 시켜 파일을 관리를 했던 반면

이번 프로젝트는 Object Storage를 이용한 파일 관리를 진행하기로 해서 프로젝트를 진행하면서 발생했던 이슈에 대해서 해결하는 과정을 작성하도록 하겠습니다.

 

1. 전체 공개 파일과 public url 접근

Object Storage를 사용하면서 개발 초반에는 버킷에 모두 공개 권한으로 파일을 업로드를 하고 클라이언트에게 해당 버킷의 public 도메인을 이용하여 이미지 파일을 보여주는 방식으로 개발을 해왔습니다.

ex) https://버킷명.kr.object.ncloudstorage.com/업로드 경로/파일명 - 현재 프로젝트에서는 AWS가 아닌 NCP Cloud 환경에서 개발

이는 모든 사용자들로 하여금 해당 파일에 접근이 가능하며 읽고 쓰기가 가능하다는 보안적 이슈가 발생할 가능성이 높다고 판단을 했습니다.

 

2. API를 통한 private 접근

위와 같은 이유로 인해 저는 API 서버에서 Object Storage에 업로드 된 비공개 파일에 접근 권한이 있는 계정의 API 키를 통해 파일에 접근을 하여 서버에서 가공을 한 뒤 내려주는 방법에 대해서 고민을 했습니다.

 

2-1 Object Storage에 접근이 가능한 AmazonS3 객체 빈 등록

@Configuration
public class AWSConfiguration {

	// API 접근 키
    private String accessKey ="";
    // API 접근 시크릿키
    private String secretKey ="";
    // Object Storage 리전
    private String region ="";
	// Object Storage 접근 public IP
    final String endPoint ="";

    @Bean
    public AmazonS3 setS3Client() {
        AmazonS3 s3 = AmazonS3ClientBuilder.standard()
            .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, region))
            .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)))
            .build();
        return s3;
    }
}

2-2 AmazonS3 객체를 통한 Object Storage 파일 접근

@Service
@RequiredArgsConstructor
public class ImageService {

    // bean으로 등록한 AmazonS3 객체
    private final AmazonS3 s3Client;

    public S3ObjectInputStream testSample(String path){
        // 접근하고자 하는 Object Storage 버킷명
        String bucketName = "testBucket";
        try {
        	// AmazonS3 객체를 이용해 버킷에 있는 파일에 접근 후 S3Object 객체에 받아오기
            S3Object s3Object = s3Client.getObject(bucketName, path);
            // s3Object 객체의 getObjectContent() 메소드를 통해 파일을 다운로드 받아온다.
            S3ObjectInputStream s3ObjectInputStream = s3Object.getObjectContent();
            // 다운로드 받은 inputStream 형식으로 return
            return s3ObjectInputStream;
        }catch (Exception e){
        	...
        }
        return null;
    }
}

 

 

2-3 Controller에서 클라이언트로 전달

@GetMapping(value = "/testSample/**")
public ResponseEntity<byte[]> testSample(HttpServletRequest request) throws GlobalException, IOException {

    String currentPath = request.getRequestURI();
    String path = currentPath.replace("/image/testSample","");

    S3ObjectInputStream img = imageService.testSample(path);
    // S3ObjectInputStream형식을 IOUtils.toByteArray를 이용해 byte 데이터로 변환 후 
    // ResponseEntity객체로 클라이언트에 전달
    return ResponseEntity.ok()
        .contentType(MediaType.IMAGE_JPEG)
        .body(IOUtils.toByteArray(img));
}

- controller에 대해서 부연 설명을 하자면 GetMapping 에 "/testSample/**" 값을 설정해두었다. 이는 "/testSample" 이하 경로에 오는 모든 요청을 받겠다는 의미로 **에는 파일 경로를 받아 온다고 가정을 했다. 

ex) https://서비스도메인/image/testSample/파일경로.jpg

- request.getRequestURI() 메소드를 통해 넘어온 URI에서 'image/testSample' 을 제거한 하위 파일 경로만 패스로 사용

- return에서 MediaType.IMAGE_JPEG를 사용했지만 이건 동적으로 파일 확장자에 따라 바꾸는 추가 코드를 작성을 하면 된다.

 

 

3. 클라이언트쪽 코드

// 변경 전
<img src="https://Object Storage public 도메인/파일경로.jpg">
// 변경 후
<img src="https://서비스도메인/파일경로.jpg">

 

4. 결론

위와 같이 코드를 작성을 해서 Object Storage에 올라가있는 전체 권한의 파일을 Object Storage의 public 도멘인으로 모든 사용자들이 접근을 할 수 있는게 아니라 API 서버를 통해 비공개 파일을 내려줌으로서 보안적으로 조금 더 안정적인 시스템이 되지 않을까 생각한다.

이번 포스팅에서는 img src에 파일 경로를 노출을 했었지만 이는 더 나아가 DB에서 이미지를 관리하고 파일 경로보다는 DB에서 관리는 이미지의 고유 시퀀스를 넘겨줘서 비지니스 로직에서 DB 조회를 통해 파일 경로를 가져오는게 더 보안적인 측면에서 좋은 방법이지만 

테스트 코드로 간단하게 포스팅을 하고자 위와 같은 방식으로 작성을 하였습니다.

 

물론 제가 작성한 방법이 가장 효율적인 방법이 아니라 다른 방법이 많이 있겠지만 지금으로써는 더 좋은 방법이 떠오르지 않는다.

 

혹시 이 글을 읽고 다른 더 좋은 방법이 있다면 추천 부탁드리겠습니다.

 

'Spring' 카테고리의 다른 글

Spring JPA / JPA  (0) 2023.06.20
Spring AOP  (0) 2023.06.13
@Asnyc를 이용한 멀티 스레드  (0) 2023.06.08
멀티 스레드 환경을 위한 스레드 풀 (Thread pool) - ThreadPoolTaskExecutor  (0) 2023.06.06
멀티 스레드 Runnable  (0) 2023.06.06