๋์ฉ๋ ํ์ผ ์ ๋ก๋ ๊ธฐ๋ฅ์ ๋งก์ผ๋ฉด ๋ฐฑ์๋๋ ๊ณ ๋ฏผ์ ๋น ์ง๋ค.
โ์ด๊ฑธ ๋ด๊ฐ ์ง์ ๋ฐ์์ S3์ ๋ค์ ์ฌ๋ ค์ผ ํ๋?โ
โ๋ฉ๋ชจ๋ฆฌ๋ ๊ด์ฐฎ์๊น?โ
โํธ๋ํฝ, ์๊ฐ ์ด๊ณผ๋?โ
๊ทธ๋์ ๋๋ ์ ํํ๋ค.
Presigned URL
, ์ ๋ก๋๋ ํ๋ก ํธ์๊ฒ ๋๊ธฐ๊ธฐ๋ก.
์ฌ์ค ์ด์ ๋ ๋ช
ํํ๋ค.
๋ฐฑ์๋๊ฐ ์ง์ ํ์ผ์ ๋ฐ์ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ด๋ค.
Presigned URL
์ AWS S3์์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ผ๋ก,
ํด๋ผ์ด์ธํธ(ํ๋ก ํธ์๋)๊ฐ ๋ฐฑ์๋๋ฅผ ๊ฑฐ์น์ง ์๊ณ ๋ฐ๋ก S3์ ์ ๋ก๋ํ ์ ์๊ฒ ํด์ฃผ๋
์์ ์ ๊ทผ ๊ถํ URL์ด๋ค.
์ด ๊ธฐ๋ฅ์ ํ์ฉํ๋ฉด, ๋ฐฑ์๋๋ ์ ๋ก๋์ ๊ด๋ จ๋ ๋ฌด๊ฑฐ์ด ์์ ๋ค(ํ์ผ ์ ์ฅ, ์ ์ก, ๋ฆฌ์์ค ๊ด๋ฆฌ ๋ฑ)์ ํ๋ก ํธ์๊ฒ ๋๊ธฐ๊ณ ๋น ์ง ์ ์๋ค.
ํ๋ง๋๋ก, ํ์ผ์ ๋ค๊ฐ ์ฌ๋ คโฆ ๋๋ URL๋ง ์ค๊ฒ ์ ๋ต์ธ ์ ์ด๋ค.
โ
์๋ฒ ๋ฆฌ์์ค ์ ์ฝ
๋ฐฑ์๋๊ฐ ์ง์ ํ์ผ์ ๋ฐ์ง ์์ผ๋ฏ๋ก, ๋ฉ๋ชจ๋ฆฌ๋ ๋์คํฌ I/O์ ๋ถ๋ด์ด ์๋ค.
โ
ํธ๋ํฝ ์ ๊ฐ
ํ์ผ์ด ๋ฐฑ์๋๋ฅผ ๊ฑฐ์น์ง ์๊ณ ๋ฐ๋ก S3๋ก ์ ์ก๋๊ธฐ ๋๋ฌธ์ ํธ๋ํฝ์ด ์ ๋ฐ์ผ๋ก ์ค์ด๋ ๋ค.
โ
์งํต ๊ตฌ์กฐ
ํ๋ก ํธ์๋ โ S3๋ก ๋ฐ๋ก PUT ์์ฒญ. ๋ฐฑ์๋๋ URL๋ง ๋ฐ๊ธํ๊ณ ๋น ์ง๋ค.
โ
๋ณด์์ฑ ํ๋ณด
Presigned URL์๋ ๋ง๋ฃ ์๊ฐ์ด ์ค์ ๋์ด ์๊ณ , ๊ทธ ์๊ฐ์ด ์ง๋๋ฉด ํด๋น URL์ ๋ฌดํจ๊ฐ ๋๋ค. ์ฆ, URL์ด ์ ์ถ๋์ด๋ ์ผ์ ์๊ฐ ์ดํ์๋ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ณด์์ฑ๋ ์ฑ๊ธธ ์ ์๋ค.
์ฒ์ ์ฌ์ฉํ ๋ ํ๋ก ํธ/๋ฐฑ์ ์ญํ ์ด ๊ต์ฅํ ํท๊ฐ๋ ธ๋ ๊ธฐ์ต์ด ์์ด์ ๊ตฌํ ํ๋ฆ์ ์ ๋ฆฌํด๋ณด์๋ค.
ํ๋ก ํธ
1. ์
๋ก๋ํ ํ์ผ ์ ๋ณด(์: ํ์ผ๋ช
, ๋๋ ํ ๋ฆฌ)๋ฅผ ๋ฐฑ์๋์ ์ ๋ฌ
๋ฐฑ์๋
2. ์์ฒญ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก Presigned URL ์์ฑ ํ ํ๋ก ํธ์ ์๋ต
ํ๋ก ํธ
3. ๋ฐ์ Presigned URL๋ก ์ง์ AWS S3์ PUT ์์ฒญํ์ฌ ํ์ผ ์
๋ก๋
ํ๋ก ํธ
4. S3 ๊ฐ์ฒด URL(=ํ์ผ ๊ณ ์ ์ฃผ์)์ ์ถ์ถํ์ฌ ๋ฐฑ์๋์ ์ ์ก
๋ฐฑ์๋
5. ๊ฐ์ฒด URL์ DB ๋ฑ์ ์ ์ฅ
์ด์ (S3์ ๋ํ IAM, ๋ฒํท ์ค์ ์ด ์๋ฃ๋์ด ์๋ค๋ ์ ์ ํ์) ์คํ๋ง๋ถํธ๋ก Presigned URL์ ์์ฑํ๋ API๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด์
/api/s3/presigned-url
๋ก POST ์์ฒญ์ ๋ณด๋ด๋ฉด presigned URL์ ์๋ตํด์ฃผ๋ ์ปจํธ๋กค๋ฌ์ด๋ค.
@RequiredArgsConstructor
@RequestMapping("/api/s3")
@RestController
public class S3Controller{
private final S3Service s3Service;
// S3 presigned URL ์์ฑ API
@PostMapping("/presigned-url")
public ResponseEntity<SuccessResponse<?>> generatePresignedUrl(@RequestBody PresignedUrlRequestDTO presignedUrlRequest) {
PresignedUrlResponseDTO presignedUrl = s3Service.generatePresignedUrl(presignedUrlRequest);
return SuccessResponse.ok(presignedUrl);
}
}
RequestDTO - ์์ฒญ์์๋ Presigned Url ์์ฑํ ํ์ํ ์ ๋ณด๋ฅผ ๋ฐ์์ฌ๊ฑด๋ฐ ์ฌ๊ธฐ์๋ ์ ๋ก๋ํ ํ์ผ์ ๊ฒฝ๋ก์ ์ด๋ฆ์ ํฌํจํ ์ ๋ณด๋ฅผ ๋ฐ์์๋ค.
public record PresignedUrlRequestDTO(
String directory,
String fileName
) {
}
ResponseDTO - ์์ฑ๋ presigned URL์ ๊ฐ์ธ์ ํด๋ผ์ด์ธํธ์ ์๋ตํ๋ค.
public record PresignedUrlResponseDTO(
String presignedUrl
) {
public static PresignedUrlResponseDTO of(String presignedUrl) {
return new PresignedUrlResponseDTO(presignedUrl);
}
}
@Service
@RequiredArgsConstructor
@Slf4j
public class S3Service {
private final AmazonS3 amazonS3;
@Value("${cloud.aws.s3.bucket}")
private String bucketName;
// presigned URL ์์ฑ
public PresignedUrlResponseDTO generatePresignedUrl(PresignedUrlRequestDTO presignedUrlRequest) {
String filePath = presignedUrlRequest.directory() + "/" + presignedUrlRequest.fileName();
Date expiration = new Date(System.currentTimeMillis() + 1000 * 60 * 15); // 15๋ถ ์ ํจ
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, filePath)
.withMethod(HttpMethod.PUT)
.withExpiration(expiration);
URL url = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
return PresignedUrlResponseDTO.of(url.toString());
}
}
์ด์ ๊ตฌํํ Presigned URL API ๊ฒฐ๊ณผ๋ฅผ ํ์ธํด๋ณด์
POST /api/s3/presigned-url
์์ฒญ์ ํตํด Presigned URL์ ๋ฐ๊ธ๋ฐ๋๋ค. ์์ฒญ ์์๋ ์
๋ก๋ํ ํ์ผ์ ๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก(directory)์ ํ์ผ๋ช
(fileName)์ ํจ๊ป ์ ๋ฌํ๋ค.
POST /api/s3/presigned-url
{
"directory": "profile",
"fileName": "profile_user1"
}
์๋ต์ผ๋ก ๋ค์๊ณผ ๊ฐ์ presigned URL์ ๋ฐ๋๋ค. ์ด presigned URL์ PUT ๋ฉ์๋๋ฅผ ํตํด ์ ํจ ์๊ฐ ๋ด์ S3์ ์ง์ ํ์ผ์ ์ ๋ก๋ํ ์ ์๋๋ก ํด์ค๋ค.
{
"presignedUrl": "https://[your-bucket].s3.ap-northeast-2.amazonaws.com/profile/profile_user1?X-Amz-Algorithm=..."
}
๋ฐ๊ธ๋ฐ์ Presigned URL๋ก PUT ์์ฒญ์ ๋ณด๋ด ๊ฐ์ฒด(์ด๋ฏธ์ง) ํ์ผ์ ์ ๋ก๋ํ๋ค.
PUT
https://[your-bucket].s3.ap-northeast-2.amazonaws.com/profile/profile_user1?X-Amz-...
์์ฒญ์ด ์ฑ๊ณตํ๋ฉด ํด๋น ์ด๋ฏธ์ง๊ฐ S3 ๋ฒํท์ ์ ์ ์ ๋ก๋๋๋ค.
Presigned URL์ ์ธ์ฆ์ฉ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ฅผ ํฌํจํ๋ฏ๋ก,
์ค์ ์ด๋ฏธ์ง ์ ๊ทผ์ ์ํ ๊ฐ์ฒด URL์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๊ฑฐํ ํํ๋ก ์ฌ์ฉํ๋ค.
Presigned URL:
https://[your-bucket].s3.ap-northeast-2.amazonaws.com/profile/profile_user1?X-Amz-Algorithm=...
โ ๊ฐ์ฒด URL:
https://[your-bucket].s3.ap-northeast-2.amazonaws.com/profile/profile_user1
์ด ๊ฐ์ฒด URL์ ํ๋ก ํธ์๋์์ Presigned URL์์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๊ฑฐํด ์ถ์ถํ๊ฑฐ๋, ๋ฐฑ์๋๊ฐ ๋ณ๋ ์์ฑ ๋ก์ง์ ๊ตฌํํ ์๋ ์๋ค.
์ด๋ ๊ฒ ์ถ์ถ๋ ๊ฐ์ฒด URL์ ํ๋ก ํธ์์ ๋ฐฑ์๋๋ก ์ ๋ฌํ๋ฉด, ๋ฐฑ์๋๋ ์ด๋ฅผ DB์ ์ ์ฅํ์ฌ ์ด๋ฏธ์ง ์๋ณ ๋ฐ ์ ๊ทผ ๊ฒฝ๋ก๋ก ํ์ฉํ ์ ์๋ค.
์ดํ ์ด๋ฏธ์ง ์ ๋ก๋๊ฐ ํ์ํ ๋ค๋ฅธ API์์๋, ์์์ ์ถ์ถํ ๊ฐ์ฒด URL ๋ฌธ์์ด์ ์์ฒญ ๋ณธ๋ฌธ์ ํฌํจํ์ฌ ์ ๋ฌํ๋ค.
POST /api/user/profile
{
"userId": "user1",
"profileImageUrl": "https://[your-bucket].s3.ap-northeast-2.amazonaws.com/profile/profile_user1"
}
์ด ๋ฐฉ์์ผ๋ก ๋ค์ํ ์๋ํฌ์ธํธ์์ ์ด๋ฏธ์ง ๋ฑ์ ๊ฐ์ฒด URL์ ์ผ๊ด์ฑ ์๊ฒ ํ์ฉํ ์ ์๋ค.
Presigned URL์ ๋ฐฑ์๋ ์๋ฒ๊ฐ ์ง์ ํ์ผ์ ์ฒ๋ฆฌํ์ง ์๊ณ ๋,
์์ ์ ์ด๊ณ ๋ณด์์ฑ ์๋ ์
๋ก๋ ๊ตฌ์กฐ๋ฅผ ์ค๊ณํ ์ ์๋ ์์ฃผ ์ข์ ๋ฐฉ๋ฒ์ด๋ค.
๋ฐ๋ผ์ S3 ์ ๋ก๋๊ฐ ํ์ํ ํ๋ก์ ํธ๋ผ๋ฉด,
"์ง์ ๋ฐ์ง ๋ง๊ณ , URL๋ง ์ฃผ์."