클라이언트가 앱을 이용해 이미지를 업로드 하게되면 해당 이미지를 s3 buket에 저장해야한다.
앱에서 다이렉트로 s3로 접근하여 데이터를 생성하게 되면 보안에 큰 문제가 될 수 있으며 상대적으로 큰 이미지 데이터를 서버로 보내 처리한다는 것이 내키지않는다.
따라서 클라이언트가 서버에게 일정시간동안 데이터를 쓸 수 있는 권한이 주어진 url을 발급받아 데이터를 업로드하는 방식을 사용하게 되었다. (서버에서 정해준 url이며 유효기간이 만료되면 데이터는 생성되지 않는다)
코드
//aws-config.ts
import { ConfigService } from '@nestjs/config';
import { S3Client } from '@aws-sdk/client-s3';
let s3;
let s3BucketUrl;
//해당 이니셜라이즈를 필자는 app이 구동되는 시점(main.ts)에서 적용하였다.
export function initializeAWS(configService: ConfigService) {
//s3 관련 필요한 키값
const awsAccessKey = configService.get('AWS_ACCESS_KEY');
const awsSecretKey = configService.get('AWS_SECRET_KEY');
s3BucketUrl = configService.get('AWS_S3_BUCKET_URL');
s3 = new S3Client({
region: 'ap-northeast-2',
credentials: {
accessKeyId: awsAccessKey,
secretAccessKey: awsSecretKey,
},
});
}
export { s3, s3BucketUrl };
AccessKey와 SecretKey는 aws IM에서 사용자를 추가하여 S3 서비스 및 필요한 권한을 등록한 다음 액세스키 생성을 통해 공급받은 값들을 사용하면 된다.
import { s3, s3BucketUrl } from 'src/aws-config';
import { UploadImgDto } from 'src/user/dto/upload-image.dto';
import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
export const getSignedUrlFunction = async (
key: string,
): Promise<UploadImgDto> => {
const params = {
Bucket: 'xx-xx-bucket', //준비한 bucket
Key: `${key}`,
ContentType: 'image/jpeg',
};
const command = new PutObjectCommand(params);
//put은 쓰기, get은 읽기? 저장?, 공식 문서에 따르면 다양한 기능들이 있다.
const url = await getSignedUrl(s3, command, { expiresIn: 3600 });
return new UploadImgDto(url, `${s3BucketUrl}/${key}`);
};
이렇게 준비해주고 적용할 api에 가져다 쓰면된다.
다음과 같은 return값을 보내준다.
PS. ec2에 처음 작성한 코드가 build가 되지 않는 현상이 있어 고생을 했었는데 원인은
import * as AWS from 'aws-sdk';
이 코드이다 로컬에서는 발견되지 않지만 ec2에서 위 코드는 문제를 발생시킨다. ec2가 aws서비스라 그런가... 잘 모르겠다...