Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DDING-000] 파일 폼지 응답 presignedUrl 발급 API 구현 #270

Merged
merged 1 commit into from
Feb 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion src/main/java/ddingdong/ddingdongBE/file/api/S3FileAPi.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,39 @@ public interface S3FileAPi {
@SecurityRequirement(name = "AccessToken")
@GetMapping("/upload-url")
UploadUrlResponse getPreSignedUrl(@AuthenticationPrincipal PrincipalDetails principalDetails,
@RequestParam("fileName") String fileName);
@RequestParam("fileName") String fileName);

@Operation(summary = "폼 응답 - AWS S3 presignedUrl 발급 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "폼 응답 presignedUrl 발급 성공"),
@ApiResponse(responseCode = "400",
description = "AWS 오류(서버 오류)",
content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ErrorResponse.class),
examples = {
@ExampleObject(name = "AWS 서비스 오류(서버 오류)",
value = """
{
"status": 500,
"message": "AWS 서비스 오류로 인해 Presigned URL 생성에 실패했습니다.",
"timestamp": "2024-08-22T00:08:46.990585"
}
"""
),
@ExampleObject(name = "AWS 클라이언트 오류(서버 오류)",
value = """
{
"status": 500,
"message": "AWS 클라이언트 오류로 인해 Presigned URL 생성에 실패했습니다.",
"timestamp": "2024-08-22T00:08:46.990585"
}
"""
)
})
)
})
@ResponseStatus(HttpStatus.OK)
@GetMapping("/upload-url/form-application")
UploadUrlResponse getFormApplicationPreSignedUrl(@RequestParam("fileName") String fileName);
Comment on lines +61 to +92
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

보안 검토가 필요합니다.

새로 추가된 엔드포인트에 다음과 같은 보안 관련 문제가 있습니다:

  1. @SecurityRequirement 어노테이션이 누락되어 있습니다.
  2. 인증되지 않은 사용자가 presigned URL을 발급받을 수 있습니다.

기존 /upload-url 엔드포인트와 동일한 수준의 보안이 필요해 보입니다.

다음과 같이 수정을 제안합니다:

    @Operation(summary = "폼 응답 - AWS S3 presignedUrl 발급 API")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "폼 응답 presignedUrl 발급 성공"),
            @ApiResponse(responseCode = "400",
                    description = "AWS 오류(서버 오류)",
                    content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
                            schema = @Schema(implementation = ErrorResponse.class),
                            examples = {
                                    @ExampleObject(name = "AWS 서비스 오류(서버 오류)",
                                            value = """
                                                    {
                                                      "status": 500,
                                                      "message": "AWS 서비스 오류로 인해 Presigned URL 생성에 실패했습니다.",
                                                      "timestamp": "2024-08-22T00:08:46.990585"
                                                    }
                                                    """
                                    ),
                                    @ExampleObject(name = "AWS 클라이언트 오류(서버 오류)",
                                            value = """
                                                    {
                                                      "status": 500,
                                                      "message": "AWS 클라이언트 오류로 인해 Presigned URL 생성에 실패했습니다.",
                                                      "timestamp": "2024-08-22T00:08:46.990585"
                                                    }
                                                    """
                                    )
                            })
            )
    })
    @ResponseStatus(HttpStatus.OK)
+   @SecurityRequirement(name = "AccessToken")
    @GetMapping("/upload-url/form-application")
-   UploadUrlResponse getFormApplicationPreSignedUrl(@RequestParam("fileName") String fileName);
+   UploadUrlResponse getFormApplicationPreSignedUrl(@AuthenticationPrincipal PrincipalDetails principalDetails,
+                                                   @RequestParam("fileName") String fileName);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Operation(summary = "폼 응답 - AWS S3 presignedUrl 발급 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "폼 응답 presignedUrl 발급 성공"),
@ApiResponse(responseCode = "400",
description = "AWS 오류(서버 오류)",
content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ErrorResponse.class),
examples = {
@ExampleObject(name = "AWS 서비스 오류(서버 오류)",
value = """
{
"status": 500,
"message": "AWS 서비스 오류로 인해 Presigned URL 생성에 실패했습니다.",
"timestamp": "2024-08-22T00:08:46.990585"
}
"""
),
@ExampleObject(name = "AWS 클라이언트 오류(서버 오류)",
value = """
{
"status": 500,
"message": "AWS 클라이언트 오류로 인해 Presigned URL 생성에 실패했습니다.",
"timestamp": "2024-08-22T00:08:46.990585"
}
"""
)
})
)
})
@ResponseStatus(HttpStatus.OK)
@GetMapping("/upload-url/form-application")
UploadUrlResponse getFormApplicationPreSignedUrl(@RequestParam("fileName") String fileName);
@Operation(summary = "폼 응답 - AWS S3 presignedUrl 발급 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "폼 응답 presignedUrl 발급 성공"),
@ApiResponse(responseCode = "400",
description = "AWS 오류(서버 오류)",
content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ErrorResponse.class),
examples = {
@ExampleObject(name = "AWS 서비스 오류(서버 오류)",
value = """
{
"status": 500,
"message": "AWS 서비스 오류로 인해 Presigned URL 생성에 실패했습니다.",
"timestamp": "2024-08-22T00:08:46.990585"
}
"""
),
@ExampleObject(name = "AWS 클라이언트 오류(서버 오류)",
value = """
{
"status": 500,
"message": "AWS 클라이언트 오류로 인해 Presigned URL 생성에 실패했습니다.",
"timestamp": "2024-08-22T00:08:46.990585"
}
"""
)
})
)
})
@ResponseStatus(HttpStatus.OK)
@SecurityRequirement(name = "AccessToken")
@GetMapping("/upload-url/form-application")
UploadUrlResponse getFormApplicationPreSignedUrl(@AuthenticationPrincipal PrincipalDetails principalDetails,
@RequestParam("fileName") String fileName);


}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,15 @@ public UploadUrlResponse getPreSignedUrl(PrincipalDetails principalDetails, Stri
URL presingedUrl = s3FileService.getPresignedUrl(query.generatePresignedUrlRequest());
return UploadUrlResponse.of(query, presingedUrl);
}

@Override
public UploadUrlResponse getFormApplicationPreSignedUrl(String fileName) {
LocalDateTime now = LocalDateTime.now();
String decodedFileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8);
GeneratePreSignedUrlRequestQuery query =
s3FileService.generatePresignedUrlRequest(
new GeneratePreSignedUrlRequestCommand(now, 9999L, decodedFileName));
URL presingedUrl = s3FileService.getPresignedUrl(query.generatePresignedUrlRequest());
return UploadUrlResponse.of(query, presingedUrl);
}
Comment on lines +35 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

코드 개선이 필요합니다.

다음과 같은 문제점들이 있습니다:

  1. 코드 중복
  • getPreSignedUrl와 대부분의 로직이 중복됩니다.
  • 공통 로직을 private 메소드로 추출하는 것이 좋습니다.
  1. 보안 문제
  • 하드코딩된 사용자 ID (9999L)는 임시 방편으로 적절하지 않습니다.
  • 실제 사용자 식별이 필요하거나, 시스템 레벨의 식별자를 사용해야 합니다.
  1. 입력 검증 부재
  • fileName 파라미터에 대한 유효성 검사가 없습니다.
  • 파일 확장자 검증이나 파일명 길이 제한 등이 필요할 수 있습니다.

다음과 같은 개선을 제안합니다:

+ private UploadUrlResponse generatePreSignedUrl(Long userId, String fileName) {
+     LocalDateTime now = LocalDateTime.now();
+     String decodedFileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8);
+     GeneratePreSignedUrlRequestQuery query =
+             s3FileService.generatePresignedUrlRequest(
+                     new GeneratePreSignedUrlRequestCommand(now, userId, decodedFileName));
+     URL presingedUrl = s3FileService.getPresignedUrl(query.generatePresignedUrlRequest());
+     return UploadUrlResponse.of(query, presingedUrl);
+ }

  @Override
  public UploadUrlResponse getPreSignedUrl(PrincipalDetails principalDetails, String fileName) {
-     LocalDateTime now = LocalDateTime.now();
-     String decodedFileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8);
-     GeneratePreSignedUrlRequestQuery query =
-             s3FileService.generatePresignedUrlRequest(
-                     new GeneratePreSignedUrlRequestCommand(now, user.getId(), decodedFileName));
-     URL presingedUrl = s3FileService.getPresignedUrl(query.generatePresignedUrlRequest());
-     return UploadUrlResponse.of(query, presingedUrl);
+     validateFileName(fileName);
+     return generatePreSignedUrl(principalDetails.getUser().getId(), fileName);
  }

  @Override
  public UploadUrlResponse getFormApplicationPreSignedUrl(String fileName) {
+     validateFileName(fileName);
-     LocalDateTime now = LocalDateTime.now();
-     String decodedFileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8);
-     GeneratePreSignedUrlRequestQuery query =
-             s3FileService.generatePresignedUrlRequest(
-                     new GeneratePreSignedUrlRequestCommand(now, 9999L, decodedFileName));
-     URL presingedUrl = s3FileService.getPresignedUrl(query.generatePresignedUrlRequest());
-     return UploadUrlResponse.of(query, presingedUrl);
+     // TODO: 시스템 레벨 식별자나 실제 사용자 ID를 사용하도록 수정
+     return generatePreSignedUrl(9999L, fileName);
  }

+ private void validateFileName(String fileName) {
+     if (fileName == null || fileName.trim().isEmpty()) {
+         throw new IllegalArgumentException("파일 이름은 필수입니다.");
+     }
+     // TODO: 파일 확장자 및 길이 검증 로직 추가
+ }

사용자 ID 처리 방식에 대해 팀과 논의가 필요합니다. 다음 중 하나를 선택하시기 바랍니다:

  1. 실제 사용자 인증 추가
  2. 시스템 레벨 식별자 사용
  3. 폼 응답 전용 경로 구조 설계
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public UploadUrlResponse getFormApplicationPreSignedUrl(String fileName) {
LocalDateTime now = LocalDateTime.now();
String decodedFileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8);
GeneratePreSignedUrlRequestQuery query =
s3FileService.generatePresignedUrlRequest(
new GeneratePreSignedUrlRequestCommand(now, 9999L, decodedFileName));
URL presingedUrl = s3FileService.getPresignedUrl(query.generatePresignedUrlRequest());
return UploadUrlResponse.of(query, presingedUrl);
}
public class S3FileController {
// ... other fields and methods ...
@Override
public UploadUrlResponse getPreSignedUrl(PrincipalDetails principalDetails, String fileName) {
- LocalDateTime now = LocalDateTime.now();
- String decodedFileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8);
- GeneratePreSignedUrlRequestQuery query =
- s3FileService.generatePresignedUrlRequest(
- new GeneratePreSignedUrlRequestCommand(now, user.getId(), decodedFileName));
- URL presingedUrl = s3FileService.getPresignedUrl(query.generatePresignedUrlRequest());
- return UploadUrlResponse.of(query, presingedUrl);
+ validateFileName(fileName);
+ return generatePreSignedUrl(principalDetails.getUser().getId(), fileName);
}
@Override
public UploadUrlResponse getFormApplicationPreSignedUrl(String fileName) {
- LocalDateTime now = LocalDateTime.now();
- String decodedFileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8);
- GeneratePreSignedUrlRequestQuery query =
- s3FileService.generatePresignedUrlRequest(
- new GeneratePreSignedUrlRequestCommand(now, 9999L, decodedFileName));
- URL presingedUrl = s3FileService.getPresignedUrl(query.generatePresignedUrlRequest());
- return UploadUrlResponse.of(query, presingedUrl);
+ validateFileName(fileName);
+ // TODO: 시스템 레벨 식별자나 실제 사용자 ID를 사용하도록 수정
+ return generatePreSignedUrl(9999L, fileName);
}
+ private UploadUrlResponse generatePreSignedUrl(Long userId, String fileName) {
+ LocalDateTime now = LocalDateTime.now();
+ String decodedFileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8);
+ GeneratePreSignedUrlRequestQuery query =
+ s3FileService.generatePresignedUrlRequest(
+ new GeneratePreSignedUrlRequestCommand(now, userId, decodedFileName));
+ URL presingedUrl = s3FileService.getPresignedUrl(query.generatePresignedUrlRequest());
+ return UploadUrlResponse.of(query, presingedUrl);
+ }
+
+ private void validateFileName(String fileName) {
+ if (fileName == null || fileName.trim().isEmpty()) {
+ throw new IllegalArgumentException("파일 이름은 필수입니다.");
+ }
+ // TODO: 파일 확장자 및 길이 검증 로직 추가
+ }
// ... potentially other methods ...
}

}