행 수준 보안(Row-Level Security, RLS)은 테이블에 정책을 걸어, 같은 쿼리를 실행해도 현재 사용자가 볼 수 있는 행만 돌려주게 하는 DB 기능이다. PostgreSQL이 대표적이다. 애플리케이션이 WHERE 조건을 빠뜨려도 DB가 막아 준다.
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.current_tenant')::int);
-- 요청마다 현재 테넌트를 세션 설정에 넣고 쿼리
SET app.current_tenant = '42';
SELECT * FROM orders; -- tenant_id = 42인 행만 보인다USING은 읽기·수정·삭제 대상이 될 행을,WITH CHECK는 새로 쓰거나 바꾼 행이 만족해야 할 조건을 정한다- 테이블 소유자와 슈퍼유저는 기본적으로 정책을 우회한다. 앱이 쓰는 DB 계정은 소유자가 아닌 별도 롤(role)로 두거나
FORCE ROW LEVEL SECURITY를 건다 - 커넥션 풀(connection pool)을 쓰면 세션 설정이 다음 요청으로 새지 않게 트랜잭션 단위(
SET LOCAL)로 설정한다
전용 DB와의 관계
RLS는 멀티테넌시에서 공유 DB로 가면서도 격리를 지키는 저비용 수단이다. 테넌트마다 DB를 나누면(dedicated DB) 남의 데이터에 닿을 일이 없어 RLS가 필요 없다. 둘은 같은 문제의 다른 답이지, 한쪽이 다른 쪽을 이용하는 관계가 아니다.
장단점
- 장점: 인가(authorization) 규칙이 데이터 바로 옆에 있어, 새 API나 관리 도구가 생겨도 같은 규칙이 적용된다(인증과 인가)
- 단점: 정책 한 줄 실수가 그대로 유출로 이어지고, 복잡한 정책은 쿼리 성능에 영향을 준다. 정책을 테스트로 고정해 두는 것이 좋다
Supabase처럼 브라우저가 DB API를 직접 부르는 구조에서는 RLS가 사실상 주된 보안 경계다. 서버를 거치지 않는 요청을 어떻게 믿을지는 브라우저 요청과 서버 간 요청를 본다.