개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Supabase 보안 — RLS와 API 키 관리 체크리스트

2025년 4월 30일
Supabase보안RLSHTTPSService Key

Supabase는 빠른 백엔드 구축을 가능하게 하지만, 잘못된 설정 하나가 전체 데이터베이스를 노출시킬 수 있다. RLS(Row Level Security) 비활성화와 Service Key 클라이언트 노출이 가장 흔한 실수다. 이 체크리스트를 통해 배포 전 필수 보안 항목을 점검하자.

요청 흐름과 키 역할

클라이언트 (브라우저/앱)
        │
        │  anon key (공개 가능)
        ▼
  Supabase API Gateway
        │
        │  JWT 검증 → RLS 정책 적용
        ▼
   PostgreSQL (RLS ON)
        ▲
        │  service_role key (서버 전용)
        │
   백엔드 서버 (Next.js API Route 등)

anon key: 비로그인 사용자용 공개 키. RLS가 적용된 테이블에만 접근 가능. service_role key: RLS를 우회하는 관리자 키로 절대 클라이언트에 노출 금지.

RLS 활성화 필수

모든 테이블은 생성 즉시 RLS를 활성화해야 한다. 활성화하지 않으면 anon key만으로 전체 데이터를 읽고 쓸 수 있다.

-- 테이블 생성 후 즉시 실행
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- 본인 데이터만 조회 허용
CREATE POLICY "본인 게시물 조회"
  ON posts FOR SELECT
  USING (auth.uid() = user_id);

-- 본인 데이터만 수정 허용
CREATE POLICY "본인 게시물 수정"
  ON posts FOR UPDATE
  USING (auth.uid() = user_id);

auth.uid는 Supabase가 JWT에서 추출한 현재 사용자 ID다. RLS 정책에서 이 함수를 기준으로 행 단위 접근을 제어한다.

Service Key 서버 전용 보관

// 잘못된 예 — 클라이언트 코드에 service key 노출
const supabase = createClient(url, process.env.NEXT_PUBLIC_SERVICE_KEY);

// 올바른 예 — 서버 사이드(API Route)에서만 사용
// pages/api/admin.ts
import { createClient } from "@supabase/supabase-js";

const adminClient = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY! // NEXT_PUBLIC_ 접두사 없음
);

NEXT_PUBLIC_ 접두사가 붙은 환경변수는 번들에 포함되어 브라우저에서 노출된다. Service Key는 절대 NEXT_PUBLIC_ 접두사를 사용하지 않는다.

JWT 검증과 HTTPS 강제

Supabase 클라이언트는 기본적으로 HTTPS 엔드포인트를 사용하지만, 자체 호스팅 환경에서는 명시적으로 강제해야 한다.

const supabase = createClient(url, anonKey, {
  auth: {
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: true,
  },
  global: {
    headers: { "x-custom-header": "my-app" },
  },
});

JWT 만료 시간은 Supabase 대시보드 Authentication → Settings → JWT expiry에서 조정한다. 기본값 3600초(1시간)를 프로덕션 요건에 맞게 단축하는 것을 권장한다.

Storage Policy

Storage 버킷도 RLS와 동일한 정책 시스템을 사용한다. 버킷을 public으로 설정하면 정책 없이 전체 공개된다.

-- 인증된 사용자만 자신의 파일 업로드 허용
CREATE POLICY "본인 파일 업로드"
  ON storage.objects FOR INSERT
  WITH CHECK (
    bucket_id = 'avatars' AND
    auth.uid()::text = (storage.foldername(name))[1]
  );

보안 체크리스트

항목확인 방법
모든 테이블 RLS 활성화Dashboard → Table Editor → 자물쇠 아이콘 확인
anon key로 민감 데이터 접근 불가Postman으로 anon key 요청 후 403 확인
Service Key 환경변수 서버 전용NEXT_PUBLIC_ 접두사 없음 확인
Storage 버킷 public 설정 검토불필요한 public 버킷 제거
JWT 만료 시간 설정1시간 이하 권장
Database 직접 접근 IP 제한Dashboard → Database → Connection pooling

RLS 없는 Supabase는 인터넷에 열린 데이터베이스와 같다. 테이블 생성과 동시에 RLS를 활성화하고, Service Key는 서버 경계 밖으로 나가지 않도록 아키텍처 수준에서 보장해야 한다.

← 이전 글Supabase 환경 변수 — API 키를 안전하게 관리하는 방법
다음 글 →Supabase 프로젝트 구조 — 실무 폴더 구성 방법