본문 바로가기
무료 API 및 오픈소스 리뷰

[이미지/동영상 업로드, 서버 없이 무료로 해결하는 CDN API #07] [트러블슈팅/총정리] 프론트엔드 직접 업로드 시 반드시 마주치는 CORS 에러와 보안 해킹 방어법

by 코드메이트 2026. 4. 23.

안녕하세요! 프론트엔드 주니어들의 성장통을 가장 빠르고 시원하게 치료해 드리는 해결사, codeBricks입니다. 🧱✨

자, 대망의 [AWS S3 요금 폭탄 피하기: 서버리스 이미지 호스팅 시리즈]의 진짜 마지막 장입니다! 지금까지 우리는 백엔드 서버 없이 브라우저(React/Next.js)에서 Cloudinary, Imgur, Supabase, ImageKit, Uploadcare 같은 훌륭한 툴들을 이용해 이미지를 직접 쏘는(Direct Upload) 달콤한 꿀을 빨았습니다.

정말 편하고, 빠르고, 무엇보다 공짜라서 너무 행복했죠? 하지만... 여러분이 만든 그 완벽해 보이는 토이 프로젝트를 실무 레벨로 끌어올리려면, 혹은 이력서에 당당히 적어 내려면 반드시 마주하고 해결해야만 하는 '어둠의 영역'이 존재합니다.

"브라우저에서 외부 서버(CDN)로 직접 API를 쏘면... 내 소중한 API 키나 비밀번호가 개발자 도구(F12) 네트워크 탭에 다 보이는 거 아니야? 해커가 내 키를 복사해서 자기 맘대로 10GB짜리 영화를 수백 개 올려버리면 내 무료 크레딧은 하루아침에 작살나고 요금 폭탄 맞는 거 아냐?!"

네, 맞습니다. 프론트엔드에서의 직접 업로드는 백엔드라는 든든한 방패가 없기 때문에 보안(Security)과 예외 처리(Error Handling)에 극도로 취약합니다. 오늘 포스팅에서는 여러분의 소중한 무료 티어를 지켜내고, 프론트엔드 단에서 할 수 있는 모든 방어막을 구축하는 실전 트러블슈팅과 해킹 방어법을 낱낱이 파헤쳐 보겠습니다.

이 글을 끝까지 읽으신다면, 여러분의 포트폴리오는 단순한 토이 프로젝트를 넘어 '실무 경험이 탑재된 진짜 프로덕션'으로 퀀텀 점프하게 될 것입니다! 🚀

 

🚨 1. CORS 에러: 브라우저가 외부로 나가는 문을 닫아버릴 때

프론트엔드에서 외부 API로 데이터를 쏠 때 가장 먼저 만나는 불청객, 새빨간 CORS(Cross-Origin Resource Sharing) 에러입니다.

브라우저는 기본적으로 내 도메인(localhost:3000)에서 다른 도메인(api.cloudinary.com)으로 무언가를 몰래 보내는 것을 '보안 위협'으로 간주하고 차단합니다.

[해결책: 서비스 대시보드에서 도메인 화이트리스트 등록] 우리가 사용한 서버리스 툴들은 이 문제를 아주 쉽게 해결할 수 있도록 열어두었습니다.

  1. 각 서비스(Cloudinary, Supabase 등)의 관리자 대시보드에 접속합니다.
  2. Security 또는 Allowed Origins (CORS 설정) 메뉴를 찾습니다.
  3. 여기에 여러분이 개발 중인 http://localhost:3000과, 나중에 배포할 실제 도메인 https://my-awesome-app.vercel.app을 정확하게 등록해 줍니다. 이렇게 하면 브라우저는 "아하, 저쪽 서버가 내 도메인을 허락해 줬구나!" 하고 문을 활짝 열어줍니다. 절대 와일드카드(*)로 모든 도메인을 열어두는 짓은 하지 마세요! 누군가 남의 사이트에서 여러분의 스토리지로 접속할 수 있게 됩니다.

 

악의적인 대용량 파일과 해킹을 브라우저 프론트엔드 단에서 사전에 차단하는 용량 및 확장자 검사 방어 로직 개념도

 

💣 2. 요금 폭탄 테러 방어: 10GB 폭탄을 막아내는 프론트엔드 수문장

만약 어떤 악의적인 유저가 여러분의 게시판에 10GB짜리 불법 영화 파일을 100번 연속으로 업로드한다면 어떻게 될까요? Supabase나 Cloudinary의 무료 크레딧이 1분 만에 동나고 서비스가 멈춰버릴 겁니다.

백엔드 서버가 있다면 파일을 받기 전에 크기를 보고 컷(Cut)해버리면 되지만, 우리는 백엔드가 없죠? 반드시 프론트엔드 브라우저 단에서 파일을 서버로 쏘기 전에(Upload 버튼을 누르자마자) 크기와 확장자를 검사해야 합니다.

이 방어 로직이 없으면 이력서 면접에서 탈락 1순위입니다. 바로 실전 코드로 막아봅시다!

Copy// 업로드 함수 내부에 가장 먼저 들어가야 할 '수문장' 로직입니다.
const handleImageUpload = (event) => {
  const file = event.target.files[0];
  if (!file) return;

  // 🛡️ 방어막 1: 파일 용량 제한 (예: 5MB)
  const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB를 바이트(Byte)로 계산
  if (file.size > MAX_FILE_SIZE) {
    alert("앗! 이미지가 너무 뚱뚱해요. 5MB 이하의 사진만 올려주세요! 🙅‍♂️");
    // return 해버려서 API 호출 자체를 원천 차단합니다!
    return; 
  }

  // 🛡️ 방어막 2: 확장자(MIME Type) 검사 (이미지만 허용)
  // 악성코드(exe)나 용량 큰 동영상(mp4)이 올라가는 걸 막습니다.
  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
  if (!allowedTypes.includes(file.type)) {
    alert("삐빅! JPG, PNG, WEBP, GIF 이미지 파일만 업로드할 수 있습니다! 🚫");
    return;
  }

  // 여기까지 무사히 통과한 '가볍고 안전한 이미지'만 
  // 비로소 Cloudinary나 Supabase로 fetch(POST) 요청을 보냅니다!
  // fetch("https://api....", { body: formData })
};

어떤가요? 이 간단한 if문 두 개가 여러분의 통장을 지켜주는 세상에서 가장 든든한 경호원이 됩니다. 파일 크기가 5MB를 넘거나 확장자가 이상하면, 네트워크 통신(트래픽) 자체가 아예 발생하지 않고 브라우저 선에서 컷(Cut)시켜 버리는 것이 핵심입니다!

 

🕵️ 3. API 키 탈취 방어: 내 시크릿 키가 F12 네트워크 탭에 다 보인다고?!

프론트엔드 다이렉트 업로드의 가장 치명적인 아킬레스건입니다. React 코드 안에 여러분의 소중한 SECRET_KEY를 하드코딩해서 배포하면, 해커가 F12 개발자 도구의 'Sources' 탭이나 'Network' 탭을 열어서 여러분의 키를 1초 만에 훔쳐 갈 수 있습니다. 프론트엔드는 누구나 소스코드를 훔쳐볼 수 있는 '퍼블릭 공간'이기 때문입니다.

 

[해결책 1: Unsigned Preset (서명되지 않은 업로드) 활용하기] Cloudinary 2편에서 배운 방법이죠. 진짜 비밀번호(API Secret)는 꽁꽁 숨겨두고, 대신 업로드 전용 '이름표(Upload Preset)'만 만들어서 프론트엔드에 공개하는 겁니다. 이 이름표로는 '오직 이미지를 올리는 것'만 가능하고, 기존 이미지를 지우거나 계정 정보를 빼내는 건 절대 불가능하게 권한을 쪼개놓는 아주 안전한 방식입니다.

 

[해결책 2: Supabase의 RLS (Row Level Security)] Supabase 4편에서 배운 갓(God) 기술입니다. 프론트엔드에 노출되는 ANON_KEY는 말 그대로 '익명 키'일 뿐, 진짜 권한은 아닙니다. 해커가 이 키를 훔쳐서 남의 사진 폴더를 다 지우려고 DELETE API를 날려도, Supabase 서버가 "너 로그인 안 했네? 이 사진 주인이 아니네?" 하고 원천 차단해 버리죠.

[궁극의 해결책 3: Presigned URL (서명된 URL) 발급받기] 이건 나중에 여러분이 실무에서 AWS S3를 다루게 될 때 반드시 알아야 할 개념입니다. "브라우저야, 네가 직접 S3에 쏘면 위험하니까, 내가 아주 가벼운 백엔드 서버(혹은 Next.js API Route)를 하나 만들게. 네가 나한테 '나 사진 올릴래' 하고 요청하면, 내가 S3한테 가서 '딱 5분 동안, 5MB짜리 사진 딱 한 장만 올릴 수 있는 일회용 임시 비밀번호(Presigned URL)'를 받아다 줄게. 넌 거기에만 쏴!" 이 방식을 쓰면 프론트엔드에 그 어떤 API 키도 노출되지 않는 완벽한 보안이 완성됩니다.

 

프론트엔드 API 키 탈취를 방어하기 위한 Unsigned Preset 및 일회용 임시 서명 URL(Presigned URL) 발급 원리

 

🎯 총정리: 퀄리티의 차이는 '예외 처리'에서 나옵니다

면접관들이 주니어 개발자의 포트폴리오를 볼 때 가장 감동하는 포인트가 무엇일까요? "이미지를 올릴 수 있다"는 사실(기능)이 아닙니다.

"만약 유저가 악의적으로 10GB짜리를 올리면 어떻게 방어했나요?" "API 키가 프론트엔드에 노출되었는데, 보안 위협은 어떻게 해결했나요?"

이런 날카로운 질문에 오늘 우리가 배운 [파일 용량/확장자 검사 로직] [CORS 화이트리스트 설정], 그리고 [Upload Preset / RLS 보안 권한 분리] 개념을 조리 있게 설명할 수 있다면, 여러분은 이미 단순한 코더를 넘어 '서비스의 안정성을 고민하는 진짜 엔지니어'로 인정받게 될 것입니다.

서버리스 툴들은 프론트엔드 개발자에게 강력한 무기를 쥐여주었지만, 그 무기를 다루는 '안전장치'를 구현하는 것은 오롯이 우리의 몫입니다. 지금까지 긴 시리즈를 따라오시느라 정말 고생 많으셨습니다.

이 포스팅을 마지막으로 여러분의 프로젝트에 더 이상 엑스박스나 요금 폭탄 테러가 발생하지 않기를 바라며, codeBricks는 앞으로도 프론트엔드 개발자 여러분의 성장을 돕는 찰진 꿀팁으로 찾아오겠습니다! 감사합니다! 🚀👋