안녕하세요. 휴먼스케이프에서 개발을 하고 있는 Victoria입니다.
오늘은 Signed URL에 대해서 알아보고, Google Cloud Service에서 제공하는 Signed Url을 이용해 파일을 업로드하는 예제를 살펴보려고 합니다.
Signed URL 도입 배경
레어노트 어드민 페이지를 개발하면서 Summernote Editor에 이미지 삽입을 하기 위해 서버와 통신하였는데, 서버쪽에서 이미지 업로드를 비동기로 수행하여 업로드 될 URL을 Response로 먼저 보내주고 이미지를 업로드하는 방식으로 동작하였습니다.
클라이언트에서 Response로 받은 URL을 이용하여 Editor에 이미지를 삽입하려고 했더니, 아직 서버쪽에서 업로드를 완료하지 않은 상태에서 해당 이미지를 Load하려고 해서 에러가 나는 상황이 발생하였습니다.
이러한 상황을 Signed URL을 이용하여 클라이언트에서 직접 이미지를 업로드함으로써 서버의 부하를 줄일 수 있게 되었고, 일정 기간동안 특정 작업만 할 수 있는 URL을 생성함으로써 보안을 향상시킬 수 있게 되었습니다. 😃
Signed URL이 뭐야?
Signed URL이란 어떠한 요청을 수행하는 데 필요한 제한된 권한과 시간을 제공하는 URL입니다.
Signed URL의 예시
Signed URL은 위 사진과 같이 쿼리 문자열에 인증 정보가 포함되어 있어 사용자 인증정보가 없는 사용자도 리소스에 대한 특정 작업을 수행할 수 있습니다. 즉 Signed URL을 소유한 모든 사람이 지정된 기간동안 지정된 작업을 수행할 수 있습니다.
이 글에서는 서버에 요청이 왔을 때, 이미지를 업로드할 수 있는 Signed URL을 생성해서 Response로 전해주면 클라이언트에서 해당 Signed URL을 이용하여 이미지를 업로드하는 예시를 살펴보려고 합니다. 👀
function generateSignedUrl(fileName, contentType) {
//generate signedUrl depends on filename and contentType
const options = {
equals: ['$Content-Type', contentType], // file type
version: 'v4', // 'v4' or 'v2'
expires: Date.now() + 1000 * 60 * 60, // expires after 1 hours
action: 'write' // 'write','read','delete','resumable'
};
const file = bucket.file(fileName);
const urls = await file.getSignedUrl(options);
const publicUrl = `https://storage.googleapis.com/${CLOUD_BUCKET}/${fileName}`;
return {
'signedUrl': urls[0],
'publicUrl': publicUrl,
}
}
위 코드는 JavaScript 기반의 signedUrl을 생성하는 코드입니다.
업로드할 파일의 Content Type과 서명 버전, 만료 기간, 수행할 작업에 대한 option을 설정합니다.
GCS bucket에 업로드할 파일의 이름을 가진 파일 객체를 생성합니다.
생성한 파일 객체의 getSignedUrl이라는 함수를 1번에서 설정한 option기반으로 수행하여 url을 받아옵니다.
Options의 V4와 V2는 서명 구성 방식의 구현 차이라고 합니다. V4에서는 만료 기간이 7일을 초과하는 Signed URL을 생성할 수 없지만, V2의 경우 만료 기간을 몇 년 동안으로도 세팅이 가능합니다. 🤔
Signed URL을 이용해 클라이언트에서 실행할 HTTP 메소드를 기반으로 action을 정의해주어야 합니다. 우리는 PUT 메소드를 이용하여 이미지를 올릴 Signed URL을 생성하는 것이기 때문에 action을 write로 정의할 수 있습니다. 파라미터로 수행할 functionType받아 action을 세팅하는 방법으로 함수를 확장할 수 있겠죠?
JavaScript get Signed URL 함수 옵션: https://googleapis.dev/nodejs/storage/latest/File.html#getSignedUrl
def get_signed_url(request): if request.method == 'GET': file_name = request.GET['file_name'] content_type = request.GET['content_type'] bucket = settings.STORAGE_CLIENT.get_bucket(버킷이름) blob = bucket.blob(file_name) url = blob.generate_signed_url( version='v4', # This URL is valid for 15 minutes expiration=datetime.timedelta(minutes=15), method='PUT', content_type=content_type ) return JsonResponse({ 'status': 0, 'signed_url': url, 'public_url': blob.public_url }) return JsonResponse({'state' : 'ERROR'})
위 코드는 파이썬 기반의 Signed URL을 생성하는 함수입니다.
let xhr = new XMLHttpRequest();
xhr.open('PUT', signedUrl);
xhr.onload = () => {
this.onUploadFinish(xhr);
};
xhr.upload.onprogress = this.onUploadProgress;
xhr.onerror = this.onUploadError;
xhr.setRequestHeader('Content-Type', file.type);
xhr.send(file);
클라이언트에서는 Signed URL을 이용해서 위와 같이 파일 업로드를 수행할 수 있습니다.
이 작업을 할 때, CORS 에러를 마주할 수 있었는데요.
여기서 잠깐! CORS란? 🤔
CORS는 Cross-Origin Resource Sharing(교차 출처 리소스 공유)의 약자로 HTTP 헤더를 사용하여, 한 Origin에서 다른 Origin에 어떤 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다.
CORS 에러는 한 출처에서 다른 출처로 어떤 리소스를 요청했을 때 접근 권한이 없으면 교차 출처 요청을 차단해서 발생하는 에러로, 보통 서버에서 Access-Control-Allow-Origin 설정을 통해 요청을 허용함으로 해결할 수 있습니다.
동일한 출처인지 확인할 수 있는 조건들
서버로부터 전달받은 Signed URL을 기반으로 이미지를 업로드하려고 할 때, 클라이언트의 Origin과 Signed URL(GCS 버킷의 URL)의 Origin이 달라서 CORS 에러가 발생했습니다.
[{
'origin': ['*'],
'responseHeader': [
'Content-Type',
'Access-Control-Allow-Origin',
'x-goog-resumable'
],
'method': ['GET','PATCH','DELETE','OPTIONS'],
'maxAgeSeconds': 3600
}]
그래서 위와 같은 cors-setting.json 파일을 이용해 버킷의 CORS 옵션을 세팅해줌으로써 문제를 해결 할 수 있었습니다.
gsutil cors set cors-json-file.json gs://버킷이름
위 명령어를 통해 버킷에 cors 옵션을 세팅할 수 있고,
gsutil cors get gs://버킷이름
위 명령어를 통해 해당 버킷의 세팅 정보를 확인하실 수 있습니다.
지금까지 Signed URL에 대해서 알아보고 간단한 예시를 확인해보셨습니다.
더 자세하게 알아보고 싶으신 분들은 아래 링크를 참고해주세요!
읽어주셔서 감사합니다!!😃
Signed URLs | Cloud Storage | Google Cloud This page provides an overview of signed URLs, which you use to give time-limited resource access to anyone in…cloud.google.com
Uploading images directly to Cloud Storage by using Signed URL | Google Cloud Blog Don't forget to turn on --retry. In case there's a temporary error case, the background function should be made with…cloud.google.com
Get to know us better! Join our official channels below.
Telegram(EN) : t.me/Humanscape KakaoTalk(KR) : open.kakao.com/o/gqbUQEM Website : humanscape.io Medium : medium.com/humanscape-ico Facebook : www.facebook.com/humanscape Twitter : twitter.com/Humanscape_io Reddit : https://www.reddit.com/r/Humanscape_official Bitcointalk announcement : https://bit.ly/2rVsP4T Email : support@humanscape.io