AWS PresignUrl

강승규·2026년 7월 31일

TIL - LV3. 프로필 사진 기능 추가와 권한 관리 (S3)

목표

팀원 프로필 사진을 서버 디스크가 아닌 S3에 저장한다. 서버 디스크에 저장하면 서버가 죽을 때 사진도 함께 날아가기 때문이다. 이미지는 Presigned URL을 통해서만 접근 가능하도록 구현한다.


진행한 것

인프라

  • S3 버킷 생성, 모든 퍼블릭 액세스 차단 옵션 켜기
  • Access Key를 코드에 넣지 않고, 기존 EC2용 IAM Role에 S3 접근 정책을 추가 (s3:PutObject, s3:GetObject)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject"],
      "Resource": ["arn:aws:s3:::cloudproject-prod-profile-images/*"]
    }
  ]
}

API

  • POST /api/members/{id}/profile-image — MultipartFile로 이미지를 받아 S3에 업로드하고, 저장된 key를 DB에 업데이트
  • GET /api/members/{id}/profile-image — Presigned URL을 생성해 반환 (유효기간 7일)

핵심 코드

GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
        .signatureDuration(Duration.ofDays(7))   // 요구사항: 7일
        .getObjectRequest(getObjectRequest)
        .build();

String presignedUrl = s3Presigner.presignGetObject(presignRequest).url().toString();

DB에는 이미지 URL 전체가 아니라 S3 오브젝트 key만 저장했다. Presigned URL은 매번 새로 서명해야 하는 값이라, URL 자체를 저장해두면 만료됐을 때 의미가 없기 때문이다.


겪은 에러와 해결

1) YML 들여쓰기 실수로 설정값을 못 찾음

Could not resolve placeholder 'app.s3.bucket-name

spring: 안에 spring:이 중첩되어 있었고, app:이 info: 아래로 잘못 들여쓰기 되어 있었다. YAML은 들여쓰기 한 칸 차이로 완전히 다른 위치의 속성이 되어버린다는 걸 실감했다. 최상위 레벨로 위치를 바로잡아서 해결했다.

Before

info:
  team-name: ${team-name}

  app:              # info 밑에 들어가 있었음
    s3:
      bucket-name: ...

After

info:
  team-name: ${team-name}

app:                # 최상위 레벨로 이동
  s3:
    bucket-name: ...

2) Postman form-data에서 Key를 빈 채로 보냄

MissingServletRequestPartException: Required part 'file' is not present

파일 타입은 File로 잘 선택했는데, 정작 Key 이름 입력을 빠뜨림. 컨트롤러의 @RequestParam("file")과 정확히 일치하는 file을 Key 칸에 입력해야 서버가 인식한다는 걸 알게 됬다.


검증

  1. Postman에서 form-data로 이미지 업로드 → 200 OK
  2. GET /profile-image 호출 → Presigned URL 발급 확인
  3. URL 파라미터의 X-Amz-Expires=604800 확인 → 604800초 = 정확히 7일
  4. 발급된 URL을 브라우저에 붙여넣기 → 이미지가 정상적으로 표시됨

배운 것

  1. DB에는 URL이 아니라 key만 저장하는 게 맞다. Presigned URL은 요청 시점마다 새로 서명해야 하는 휘발성 값이기 때문이다.
  2. Presigned URL이 매우 긴 이유는, 짧은 토큰 하나가 아니라 서명 검증에 필요한 정보(토큰, 알고리즘, 날짜, 만료시간, 서명값) 전체가 쿼리 파라미터에 담기기 때문이다.
  3. YAML은 들여쓰기가 곧 구조이므로, 특히 여러 depth가 겹치는 설정 파일에서는 한 칸이라도 어긋나면 완전히 다른 속성으로 인식된다는 점을 항상 주의해야 한다.
  4. Access Key를 코드에 넣는 대신 IAM Role을 리소스(EC2)에 직접 연결하는 방식이, 키 유출 위험이 없어 실무에서도 표준적으로 쓰이는 이유를 체감했다.
profile
industrial engineer

0개의 댓글