아마존 S3를 이용해 보려고 하다가 실패했다.
실패 원인은 다양했는데 포기한 가장 큰 이유는
많은 예제들 중에 내가 써먹을 수 있을 만한 예제를 찾지 못했다.
그래서 Supabase DB를 사용하고 있으니, Supabas Storage도 사용해 보자! 라고 생각했다.
일단, 적용하면서 겪은 가장 큰 일은 써먹을 수 있을 만한 예제고 뭐고
Supabase Storage에 대한 예제가 매우 적다.
심지어 그 중에 Kotlin을 사용한 예제는 찾지 못했다.
다만, 최근에 서비스가 시작되어서 그런지 docs가 매우 잘 쓰여져 있어
공식 문서들을 참고하여 서비스를 구현했다.
implementation("io.github.jan-tennert.supabase:storage-kt:2.0.4")
implementation("io.ktor:ktor-client-cio:2.3.7")
storage만 추가해서 끝나는 것이 아니라 스토리지 <-> 서버 간의 데이터 송수신이
ktor을 이용하기 때문에 해당 디펜던시도 추가해 주어야 한다.
supabase:
url : "Supabase URL"
key : "API KEY"
@Component
open class SupabaseConfig {
@Value("\${supabase.url}")
lateinit var supabaseUrl: String
@Value("\${supabase.key}")
lateinit var supabaseKey: String
@Bean
fun supabaseClient() : SupabaseClient {
return createSupabaseClient(supabaseUrl, supabaseKey) {
install (Storage)
}
}
yaml에 작성된 값을 받아 사용했다.
override suspend fun submitHomework(file: MultipartFile): SubmitResponse {
val bucketName = "homework"
val filename = UUID.randomUUID().toString() + '.' + (file.originalFilename!!.split('.')[1])
val uploadedUrl = "SupabaseURL/storage/v1/object/public/$filename"
supabase.storage.from(bucketName).upload(filename, file.bytes)
return SubmitResponse(
uploadedUrl = uploadedUrl,
message = "업로드 완료."
)
}
1) 구현하며 가장 처음 문제가 되었던 것은 인증 관련 문제였다.
Supabase에서는 위와 같이 bucket 별로, 하위 내용 별로 정책을 설정할 수 있었다.
인증된 유저만 접근 및 사용을 허가하기 때문에 정확한 내용 입력이 필요했다.
주로 사용되는 정책은 템플릿으로 저장되어 있어, 약간의 수정을 통해 정책을 설정할 수 있었다.
2) 두 번째 문제도 인증 관련 문제였다.
분명 정확히 정책을 작성한 것 같은데도 아래와 같은 에러가 표시됐다.
2024-01-24T17:56:20.548+09:00 ERROR 40680 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] threw exception
io.github.jan.supabase.exceptions.BadRequestRestException: invalid token (invalid token)
URL: https://ufkrrzcfwlvxdonidpfz.supabase.co/storage/v1/object/homework/81c818f8-9cf1-4186-a127-cb74e2a4a310.gif
Headers: [Authorization=[Bearer
정확히 말하자면 위와 같은 에러는 아니었다.
지금은 API Key값이 invalid하다는 에러 표시고, 내가 마주했던 에러는
Row Level Policy 문제였다.
정책의 레벨이 낮아 업로드 과정에서 access가 거부되고 있었는데,
수 십번은 더 정책을 지웠다가 만들고, 내용을 수정했던 것 같다.
그러던 중 API Key에 문제가 있는 것이 아닌가?
라는 생각에 Key값을 변경했다.
기존에 사용하던 API Key는 anon, public에 작성된 Key였다.
정책과 행 수준 보안을 사용한다기에 냅다 아 이게 안전하겠구나~ 하면서 사용했었는데
그게 아니라 service_role에서 숨겨진 키를 확인하여 사용했어야 했다.
적용시킨 뒤 업로드가 아주 잘 됐다 !!
3) 세 번째 문제
업로드 된 파일의 URL과 Response의 URL이 서로 달라, DB에 어떻게 저장해야
내 원래 URL을 가져올 수 있을까? 였다.
val filename = UUID.randomUUID().toString() + "-" + (file.originalFilename) != "homework"
val uploadedUrl = "\${supabase.url}/storage/v1/object/public/homework/$filename"
최초에 사용했던 코드는 위와 같다.
randomUUID + 기존파일명 으로 설정했고, 파일명에 문제가 있을 경우 homework로 설정했다.
이렇게 외부 키를 사용하니 아래와 같이 URL이 설정됐다.
객체를 불러오기 때문에 실제 db의 URL이 아니었다.
이후 문제점을 깨닫고 아래와 같이 변경했다.
val filename = UUID.randomUUID().toString() + '.' + (file.originalFilename!!.split('.')[1])
먼저, 파일 명을 굳이 사용하지 않아도 UUID로 unique한 값이 되기에
파일 명을 빼고, 확장자를 가져오기 위해 split을 사용했다.
split 시 array형태로 [0]파일명.[1]exe jpeg 형식이 된다.
스프레드시트와 동일한 방식인 것 같다.
그리고 uploadUrl도 Supabase Storage URL을 그대로 하드코딩하여
잘못된 경로가 지정되지 않게끔 설정했다.
결과물
더 많은 문제를 겪긴 했지만 나의 부족함으로 인해 발생한 문제들이다.
혹시라도 누군가 supabase storage를 이용하기 위해 찾고있다면
"정말 간편한 세팅으로 쉽게 연결할 수 있다" 라고 말할 수 있을 것 같다.