





{
"Version": "2012-10-17", // AWS 정책 언어 버전
"Statement": [ // 정책의 각 규칙을 정의하는 배열
{
"Sid": "Statement1", // 정책의 고유 식별자(선택적)
"Principal": "*", // 정책이 적용될 주체(모든 사용자 의미)
"Effect": "Allow", // 허용 또는 거부의 효과(허용)
"Action": "s3:*", // 허용할 작업(s3 모든 작업 허용)
"Resource": "arn:aws:s3:::<버킷 이름>/*"
//정책이 적용될 리소스(특정 s3 버킷 내 모든 객체)
}
]
}

implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
or
implementation("com.amazonaws:aws-java-sdk-s3:1.12.174")
spring:
profiles:
active: dev
include: secret
servlet:
multipart:
enabled: true # 멀티파트 업로드 지원여부 (default: true)
file-size-threshold: 0B # 파일을 디스크에 저장하지 않고 메모리에 저장하는 최소 크기 (default: 0B)
# 임시 디렉터리에 저장된 파일은 힙 메모리가 아닌 Servlet Container Disk에 저장됨
# 즉, file-size-threshold 초과한 크기의 파일 모두 아래 경로에 임시 저장
# 업로드 중간에 실패하는 등 장애 발생 시 명시적으로 임시 파일 삭제를 위해 경로 지정 필요
# 가끔 임시 파일 삭제되지 않고 남아있는 경우 별도 삭제 작업 필요한데 이를 용이하게 하기 위해 지정
location: /users/parkcheorhyeon/temp # 업로드된 파일이 임시로 저장되는 디스크 위치 (default: WAS가 결정)
max-file-size: 100MB # 한개 파일의 최대 사이즈 (default: 1MB)
max-request-size: 100MB # 한개 요청의 최대 사이즈 (default: 10MB)
aws:
s3:
accessKey: 액세스키
secretKey: 시크릿키
bucket : 버킷명
application-secret.yml.template 파일은 업로드에 포함시켜 pull 받을 시 위 내용을 작성할 템플릿을 제공하도록은 설정 해두었습니다!@Configuration
public class S3Config {
@Value("${aws.s3.accessKey}")
private String accessKey;
@Value("${aws.s3.secretKey}")
private String secretKey;
@Bean
public AmazonS3 amazonS3Client() {
return AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)))
.withRegion(Regions.AP_NORTHEAST_2)
.build();
}
}
@RestController
@RequiredArgsConstructor
public class FileUploadController {
private final AmazonS3 amazonS3Client;
@Value("${aws.s3.bucket}")
private String bucket;
@PostMapping("/multipart-files/single")
public String uploadMultipleFile(@RequestPart MultipartFile file) throws IOException {
// 객체의 메타 data 설정 - 해두면 클라이언트에서 다운 받을 때 처리 용이
ObjectMetadata objectMetadata = new ObjectMetadata();
// 파일의 형식에 맞는 MIME 타입을 설정, size 설정 하는것이 좋음
objectMetadata.setContentType(file.getContentType());
objectMetadata.setContentLength(file.getSize());
String folderName = "yourFolderName/";
String transFolder = folderName + UUID.randomUUID() + file.getOriginalFilename();
PutObjectRequest putObjectRequest = new PutObjectRequest(
bucket, // 버킷
transFolder, // 파일명, 폴더 구분할 수 있다.
file.getInputStream(),
objectMetadata // 객체의 메타data 설정 클래스
);
// 파일을 저장 하는 메섣드
amazonS3Client.putObject(putObjectRequest);
// 파일이 저장된 URI를 return, 해당 경로 이동 시 파일 open
// 버킷 정책을 변경하지 않았으면 파일은 업로드 되지만
// 해당 URL로 이동 시 Access Denied 됨
return amazonS3Client.getUrl(bucket, transFolder).toString();
}
transFolder : s3 버킷 내 파일명 지정 가능
/로 구분되어 폴더로 생성됨/yourFolderName 이런식으로 사용하면 / 이라는 폴더도 생김
파일명 지정 : UUID를 같이 포함하여 같은 파일을 업로드 하더라도 여러번 업로드 되도록 하기 위함
putObject() 메서드가 파일을 저장해주는 메서드

@PostMapping("/multipart-files")
public LinkedHashMap<String, String> uploadMultipleFiles(@RequestPart("uploadFiles") List<MultipartFile> multipartFiles) throws IOException {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (MultipartFile file : multipartFiles) {
// 객체의 메타 data 설정 - 해두면 클라이언트에서 다운 받을 때 처리 용이
ObjectMetadata objectMetadata = new ObjectMetadata();
// 파일의 형식에 맞는 MIME 타입을 설정, size 설정 하는것이 좋음
objectMetadata.setContentType(file.getContentType());
objectMetadata.setContentLength(file.getSize());
String folderName = "yourFolderName/";
String transFolder = folderName + UUID.randomUUID() + file.getOriginalFilename();
PutObjectRequest putObjectRequest = new PutObjectRequest(
bucket, // 버킷
transFolder, // 파일명, 폴더 구분할 수 있다.
file.getInputStream(),
objectMetadata // 객체의 메타data 설정 클래스
);
amazonS3Client.putObject(putObjectRequest);
String url = amazonS3Client.getUrl(bucket, transFolder).toString();
map.put(file.getOriginalFilename(), url);
}
return map;
}
@GetMapping("/multipart-files")
public ResponseEntity<UrlResource> downloadImage(@RequestParam String originalFilename) throws
UnsupportedEncodingException {
UrlResource urlResource = new UrlResource(amazonS3Client.getUrl(bucket, originalFilename));
// prefix로 사용되는 "yourFolderName-" 제외한 다운로드 자동 되도록 설정
// attachment : 브라우저가 파일 다운로드 하도록 지시 / inline : 브라우저는 파일 직접 열려고 시도
// folderName과 UUID 길이를 제외한 파일 이름 추출
int folderNameLength = "yourFolderName/".length();
int uuidLength = 36; // UUID 길이는 항상 36자
String extractedFilename = originalFilename.substring(uuidLength + folderNameLength);
// URL 인코딩된 파일 이름 생성
String encodedFilename = URLEncoder.encode(extractedFilename, StandardCharsets.UTF_8);
// Content-Disposition 헤더에 인코딩된 파일 이름 사용
String contentDisposition = "attachment; filename=\"" + encodedFilename + "\"";
// header에 CONTENT_DISPOSITION 설정을 통해 클릭 시 다운로드 진행
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
.body(urlResource);
}
yourFolderName/a.pdf 라 했을때 SpringBoot에서 requestPath로 취급해버려서 에러남
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>파일 다운로드</title>
<script>
function downloadFile() {
const filename = 'yourFolderName/12de6241-965a-4a13-93cb-21706375c0f42024년 재개발임대주택 모집공고문 (1).pdf';
const url = `http://localhost:8080/multipart-files?originalFilename=${filename}`;
window.location.href = url; // 새 창에서 다운로드 요청
}
</script>
</head>
<body>
<h1>파일 다운로드 버튼</h1>
<button onclick="downloadFile()">다운로드</button>
</body>
</html>

@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "test";
}
}


implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // Thymeleaf 의존성 추가
@DeleteMapping("/multipart-files")
public String uploadMultipleFiles(@RequestParam String deleteFileName){
try {
amazonS3Client.deleteObject(bucket, deleteFileName);
return "삭제 성공";
} catch (AmazonServiceException e) {
return "삭제 실패: " + e.getMessage();
}
}
@RestController
@RequiredArgsConstructor
public class FileUploadController {
private final AmazonS3 amazonS3Client;
private static final String FOLDER_NAME = "yourFolderName/";
private static final int UUID_LENGTH = 36;
@Value("${aws.s3.bucket}")
private String bucket;
@PostMapping("/multipart-files/single")
public String uploadMultipleFile(@RequestPart MultipartFile file) throws IOException {
return uploadFile(FOLDER_NAME, file);
}
@PostMapping("/multipart-files")
public LinkedHashMap<String, String> uploadMultipleFiles(@RequestPart("uploadFiles") List<MultipartFile> multipartFiles) throws IOException {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (MultipartFile file : multipartFiles) {
String url = uploadFile(FOLDER_NAME, file);
map.put(file.getOriginalFilename(), url);
}
return map;
}
@DeleteMapping("/multipart-files")
public String uploadMultipleFiles(@RequestParam String deleteFileName) {
try {
amazonS3Client.deleteObject(bucket, deleteFileName);
return "삭제 성공";
} catch (AmazonServiceException e) {
return "삭제 실패: " + e.getMessage();
}
}
@GetMapping("/multipart-files")
public ResponseEntity<UrlResource> downloadImage(@RequestParam String originalFilename) {
UrlResource urlResource = new UrlResource(amazonS3Client.getUrl(bucket, originalFilename));
// prefix로 사용되는 "yourFolderName-" 제외한 다운로드 자동 되도록 설정
// attachment : 브라우저가 파일 다운로드 하도록 지시 / inline : 브라우저는 파일 직접 열려고 시도
// folderName과 UUID 길이를 제외한 파일 이름 추출
String extractedFilename = originalFilename.substring(FOLDER_NAME.length() + UUID_LENGTH);
// URL 인코딩된 파일 이름 생성
String encodedFilename = URLEncoder.encode(extractedFilename, StandardCharsets.UTF_8);
// Content-Disposition 헤더에 인코딩된 파일 이름 사용
String contentDisposition = "attachment; filename=\"" + encodedFilename + "\"";
// header에 CONTENT_DISPOSITION 설정을 통해 클릭 시 다운로드 진행
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
.body(urlResource);
}
/*
- @param folderName : s3에 업로드할 폴더명
- @param file : s3에 업로드할 파일
- desc : 업로드할 폴더명과 파일을 받아서 해당 경로에 파일 업로드 메서드
*/
public String uploadFile(String folderName, MultipartFile file) throws IOException {
ObjectMetadata objectMetadata = getObjectMetadata(file);
String transFolder = folderName + UUID.randomUUID() + file.getOriginalFilename();
PutObjectRequest putObjectRequest = new PutObjectRequest(
bucket, // 버킷
transFolder, // 파일명, 폴더 구분할 수 있다.
file.getInputStream(),
objectMetadata // 객체의 메타data 설정 클래스
);
amazonS3Client.putObject(putObjectRequest);
return amazonS3Client.getUrl(bucket, transFolder).toString();
}
/*
- @param file : s3에 업로드할 파일
- desc : 업로드할 파일을 받아서 ObjectMetaData 반환 메서드
*/
private ObjectMetadata getObjectMetadata(MultipartFile file) {
// 객체의 메타 data 설정 - 해두면 클라이언트에서 다운 받을 때 처리 용이
ObjectMetadata objectMetadata = new ObjectMetadata();
// 파일의 형식에 맞는 MIME 타입을 설정, size 설정 하는것이 좋음
objectMetadata.setContentType(file.getContentType());
objectMetadata.setContentLength(file.getSize());
return objectMetadata;
}
}
