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

© 2026 newgirok

← 글 목록

Row Level Security — 행 단위 접근 권한 제어

2025년 4월 3일
SupabaseRLSRow Level SecurityPolicy보안

데이터베이스에 접근 제어를 애플리케이션 레이어에만 의존하면, API 버그 하나로 모든 사용자 데이터가 노출될 수 있다. Row Level Security(RLS) 는 PostgreSQL이 제공하는 행 단위 접근 제어 기능으로, 데이터베이스 자체에서 "어떤 사용자가 어떤 행을 읽고 쓸 수 있는가"를 강제한다. Supabase는 이 RLS를 기본 보안 레이어로 채택하고 있다.

RLS를 사용하는 이유

애플리케이션 서버가 없는 Supabase 아키텍처를 생각해보자.

클라이언트
   │
   ▼
Supabase API (PostgREST)
   │
   ▼
PostgreSQL
   ├── Table: posts
   ├── Table: comments
   └── Table: profiles

클라이언트가 PostgREST를 통해 직접 데이터베이스에 쿼리를 날리는 구조다. 서버 미들웨어가 없으므로 권한 검사는 데이터베이스 안에서 이루어져야 한다. RLS가 비활성화된 테이블은 anon 키만 있으면 전체 행을 읽을 수 있다.

PostgREST — PostgreSQL을 REST API로 자동 노출하는 서버. Supabase가 내부적으로 사용한다.

RLS 활성화

-- RLS 활성화
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- 기본적으로 모든 접근 차단됨 (Policy 없으면 아무도 못 읽음)

RLS를 활성화하면 Policy가 없는 한 모든 행이 차단된다. 테이블 소유자(superuser)는 예외다.

Policy 작성

Policy는 USING 절(읽기/수정 대상 필터)과 WITH CHECK 절(쓰기 허용 조건)로 구성된다.

-- 본인 게시글만 SELECT 허용
CREATE POLICY "select_own_posts"
ON posts
FOR SELECT
USING (auth.uid() = user_id);

-- 본인만 INSERT 허용
CREATE POLICY "insert_own_posts"
ON posts
FOR INSERT
WITH CHECK (auth.uid() = user_id);

-- 본인 게시글만 UPDATE 허용
CREATE POLICY "update_own_posts"
ON posts
FOR UPDATE
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);

-- 본인 게시글만 DELETE 허용
CREATE POLICY "delete_own_posts"
ON posts
FOR DELETE
USING (auth.uid() = user_id);

auth.uid — Supabase가 JWT 토큰에서 추출한 현재 사용자의 UUID를 반환하는 함수.

SELECT / INSERT / UPDATE / DELETE 정책 비교

명령USINGWITH CHECK
SELECT반환할 행 필터사용 안 함
INSERT사용 안 함삽입 허용 조건
UPDATE수정 대상 행 필터수정 후 값 조건
DELETE삭제 대상 행 필터사용 안 함

실무 예시 — 공개 글 + 본인 수정

-- 공개된 게시글은 누구나 읽기 가능
CREATE POLICY "public_read_posts"
ON posts
FOR SELECT
USING (is_published = true OR auth.uid() = user_id);

-- 작성자만 수정/삭제
CREATE POLICY "author_modify_posts"
ON posts
FOR ALL
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);

FOR ALL은 INSERT/SELECT/UPDATE/DELETE 모두에 적용된다. 단, INSERT에는 USING이 무시되므로 WITH CHECK를 반드시 명시해야 한다.

Supabase 클라이언트에서 확인

const { data, error } = await supabase
  .from('posts')
  .select('*');
// RLS Policy를 통과한 행만 반환됨

서버 측에서 service_role 키를 사용하면 RLS를 우회한다. 클라이언트에 service_role 키를 절대 노출하지 말 것.

RLS는 애플리케이션 버그가 있어도 데이터베이스 레벨에서 권한을 강제하는 마지막 방어선이다. Policy를 테이블별, 작업별로 명확히 분리해두면 권한 로직을 데이터베이스 한 곳에서 관리할 수 있다.

← 이전 글Supabase Table — 데이터를 저장하는 기본 구조
다음 글 →Supabase Authentication — 사용자 인증 내장 기능