T_era

Spring Boot + AWS S3 프로필 이미지 API 구현 본문

Programing/Spring

Spring Boot + AWS S3 프로필 이미지 API 구현

블스뜸 2025. 7. 1. 15:12

🎯 개요

Spring Boot에서 AWS S3를 활용해 사용자 프로필 이미지를 관리하는 API를 구현해보기


1️⃣ 환경 설정

AWS S3 버킷 생성

  • 버킷명: your-project-bucket
  • 리전: ap-northeast-2 

환경변수 설정

DB_USERNAME=your_db_username // RDS DB master
DB_PASSWORD=your_db_password // RDS DB master password
AWS_ACCESS_KEY=your_aws_access_key
AWS_SECRET_KEY=your_aws_secret_key
AWS_REGION=ap-northeast-2
AWS_S3_BUCKET=your-project-bucket

build.gradle 의존성

implementation 'software.amazon.awssdk:s3:2.24.12'

2️⃣ 핵심 코드 구현

S3 설정 클래스

@Configuration
public class S3Config {
    @Value("${AWS_ACCESS_KEY}")
    private String accessKey;

    @Bean
    public S3Client s3Client() {
        return S3Client.builder()
                .region(Region.of(region))
                .credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
                .build();
    }
}

AWS S3 클라이언트를 Bean으로 등록하여 애플리케이션에서 사용할 수 있도록 설정한다.

S3 서비스 핵심 메서드

@Service
public class S3Service {
    public String uploadProfileImage(MultipartFile file, Long userId) {
        validateFile(file);  // 파일 검증
        String key = "profile-image/" + userId + "/" + generateFileName(file);

        s3Client.putObject(PutObjectRequest.builder()
                .bucket(bucketName)
                .key(key)
                .build(), RequestBody.fromInputStream(file.getInputStream(), file.getSize()));

        return getFileUrl(key);
    }
}

설명: 파일을 S3에 업로드하고 URL을 반환하는 메서드. 파일 검증과 중복 방지를 위한 파일명 생성도 포함.

User 엔티티 수정

@Entity
public class User {
    private String profileImageUrl;  // 추가

    public void updateProfileImage(String profileImageUrl) {
        this.profileImageUrl = profileImageUrl;
    }
}

설명: 사용자 엔티티에 프로필 이미지 URL 필드와 업데이트 메서드를 추가

컨트롤러 엔드포인트

@RestController
public class UserController {
    @PostMapping("/users/profile-image")
    public ResponseEntity<ProfileImageResponse> uploadProfileImage(
            @AuthenticationPrincipal AuthUser authUser,
            @RequestParam("image") MultipartFile image) {
        // 이미지 업로드 로직
    }

    @DeleteMapping("/users/profile-image")
    public ResponseEntity<Void> deleteProfileImage(@AuthenticationPrincipal AuthUser authUser) {
        // 이미지 삭제 로직
    }
}

설명: 이미지 업로드와 삭제를 위한 REST API 엔드포인트를 구현.


3️⃣ 배포 및 실행

JAR 빌드 및 업로드

./gradlew clean build -x test
scp -i "your-key.pem" build/libs/your-app.jar ec2-user@your-ec2-ip:~/

EC2 환경변수 설정

# 환경변수 추가
echo "export AWS_ACCESS_KEY=your_key" >> ~/.bashrc
echo "export AWS_S3_BUCKET=your_bucket" >> ~/.bashrc
source ~/.bashrc

포트 확인 및 프로세스 정리

sudo lsof -i :8080  # 포트 사용 확인
kill -9 <PID>       # 기존 프로세스 종료

서버 실행

nohup java \
-DAWS_ACCESS_KEY=${AWS_ACCESS_KEY} \
-DAWS_S3_BUCKET=${AWS_S3_BUCKET} \
-jar your-app.jar > ~/app.log 2>&1 &

4️⃣ API 테스트

회원가입 및 로그인

# 회원가입 (JWT 토큰 획득)
curl -X POST http://localhost:8080/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "password": "Test1234"}'

이미지 업로드 테스트

# 테스트 이미지 준비
curl -o test-image.jpg https://dummyimage.com/300x300/000/fff

# 이미지 업로드
curl -X POST \
  http://localhost:8080/users/profile-image \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -F "image=@test-image.jpg"

예상 결과:

{
  "imageUrl": "https://your-bucket.s3.amazonaws.com/profile-image/1/1_uuid.jpg",
  "message": "프로필 이미지가 성공적으로 업로드되었습니다."
}

사용자 정보 조회

curl -X GET \
  http://localhost:8080/users/1 \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

예상 결과:

{
  "id": 1,
  "email": "test@example.com",
  "profileImageUrl": "https://your-bucket.s3.amazonaws.com/profile-image/1/1_uuid.jpg"
}

5️⃣ 주요 트러블슈팅

포트 충돌

문제: Port 8080 was already in use
해결: sudo lsof -i :8080kill -9 <PID>

환경변수 인식 실패

문제: @Value에서 환경변수를 찾지 못함
해결: source ~/.bashrc 실행

S3 권한 에러

문제: S3 업로드 시 권한 에러
해결: IAM 사용자에게 S3 접근 권한 부여


✅ 완성된 기능

  • 이미지 업로드: S3에 안전하게 저장
  • 파일 검증: 크기(5MB), 확장자(jpg, png, gif) 검증
  • 중복 처리: 기존 이미지 자동 삭제
  • JWT 인증: 보안된 API 접근
  • 환경변수 관리: 민감 정보 보안