Skip to content

PostgreSQL:RowSecurityPolicies

CREATE POLICY 문법

CREATE POLICY 이름 ON 테이블이름
    [ AS { PERMISSIVE | RESTRICTIVE } ]
    [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
    [ TO { 롤이름 | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ]
    [ USING ( 사용식 ) ]
    [ WITH CHECK ( 검사식 ) ]

CREATE POLICY 명령은 한 테이블에 새 로우 수준 보안 정책을 설정한다.

이 명령을 사용하려면, 먼저 그 해당 테이블 속성 가운데 로우 단위 보안 정책 사용이 활성화 되어 있어야 한다.

하나의 정책이란 정책식 검사로 해당되는 로우에 한해서 select, insert, update, delete 작업을 허용하는 것을 말한다.

  • 이미 테이블에 있는 자료에 대한 검사식(SELECT, UPDATE, DELETE)은 USING 옵션으로 지정하는 표현식을 사용하며,
  • INSERT 명령이나, UPDATE 명령으로 새로 저장될 자료에 대한 검사는 WITH CHECK 옵션으로 지정한는 표현식을 사용한다.
  • USING 옵션의 표현식 결과가 true 값이면, 해당 사용자에게 그 로우를 보여주고, false나 null 이면, 보여주지 않는다.
  • WITH CHECK 옵션의 표현식 결과가 true 값이면, 해당 로우가 insert 또는 update 되며, false나 null 이면, 해당 작업을 오류로 처리한다.

INSERT 또는 UPDATE 구문 처리는 BEFORE 트리거 처리 다음에, 실 자료가 변경 되기 전에, WITH CHECK 검사식이 수행된다. 즉, BEFORE ROW 트리거에서 해당 자료를 조작한다면, 검사식 결과가 달라질 수 있다. WITH CHECK 검사식은 모든 제약 조건 검사보다 먼저 수행된다.

정책 이름은 테이블 단위로 지정한다. 같은 정책 이름이 여러 테이블에 사용할 수 있으며, 이들은 각기 그 테이블과 관련된 정책을 설정한다.

정책은 특정 명령이나, 특정 롤을 대상으로 정의할 수 있다. 특별하게 지정하지 않으면, 모든 명령, 모든 롤 대상이다. 하나의 명령으로 여러 정책을 한번에 정의할 수 있다.

하나의 정책에서 ALL 또는 UPDATE 명령에 대해 USING과 WITH CHECK 구문을 함께 지정할 수 있기 때문에, WITH CHECK 구문이 없는 경우에는, USING 에서 지정한 조건식이 자료를 조회할 때도, 자료를 조작 할 때도 같이 사용된다.

아무런 정책도 정의된 것이 없는 로우 단위 보안 기능이 활성화된 테이블이 있다면, 그 테이블의 기본 보안 정책은 "모두 금지"다. 조회할 수도, 자료를 조작할 수도 없다.

Supabase 예제

다음과 같이 테이블을 만들었다면:

create table public.profiles (
  id uuid not null references auth.users on delete cascade,
  first_name text,
  last_name text,

  primary key (id)
);

RLS는 다음과 같이 활성화 할 수 있습니다:

alter table public.profiles enable row level security;

RLS 정책은 다음과 같이 설정:

create policy "Public profiles are viewable by everyone."
  on profiles for select
  using ( true );

create policy "Users can insert their own profile."
  on profiles for insert
  with check ( auth.uid() = id );

create policy "Users can update own profile."
  on profiles for update
  using ( auth.uid() = id );

비공개 액세스(데이터를 소유한 사용자만 데이터를 읽을 수 있어야 한다)의 경우 위의 select 쿼리에 대한 변경만 하면 됩니다:

create policy "Profiles are viewable by users who created them."
  on profiles for select
  using ( auth.uid() = id );

만약 소유자가 데이터 삭제도 가능하게 하고 싶다면:

create policy "Individuals can delete their own posts."
  on posts for delete
  using ( auth.uid() = id );

이 패턴의 좋은 점은 무엇입니까? 이제 API를 통해 이 테이블을 쿼리할 수 있으며 API 쿼리에 데이터 필터를 포함할 필요가 없습니다. 정책이 이를 처리합니다.

// 사용자가 로그아웃한 동안에는 아무것도 반환하지 않습니다.
const { data } = await supabase.from('profiles').select('id, username, avatar_url, website')

// 사용자가 로그인한 후에는 로그인한 사용자의 데이터만 반환됩니다. 위 경우 단일 행만 반환.
const { error } = await supabase.auth.signIn({ email })
const { data: profile } = await supabase
  .from('profiles')
  .select('id, username, avatar_url, website')

INFORMATION

보안은 RLS 정책에 의해 데이터베이스 수준에서 처리됩니다. 정책은 반환되는 행을 제한하므로 ID에 대한 필터가 필요하지 않습니다. 테이블 크기와 쿼리에 따라 성능을 위해 필터를 추가할 수도 있습니다. Postgres는 필터를 사용하여 보다 효율적인 쿼리를 구성할 수 있습니다.

개수 제한 방법

Using security definer functions

You can use security definer functions inside Policies. This is useful in a many-to-many relationships, and important for performance. Following the teams and members example from above, this example shows how you can use the security definer function in combination with a policy to control access to the members table.

-- 1. Create a table of teams
create table teams (
  id serial primary key,
  name text
);

-- 2. Create many to many join
create table members (
  team_id bigint references teams,
  user_id uuid references auth.users
);

-- 2.  Enable RLS
alter table teams enable row level security;
alter table members enable row level security;

-- 3.  Create security definer function, which should be run as "postgres"
create function private.get_teams_for_authenticated_user()
returns setof bigint
language sql
security definer -- 실행 권한을 실행한 측이 아닌 함수 정의한 측(definer)로 지정.
set search_path = public -- 검색 경로 'public' schema 로 제한
stable -- 내용을 수정하지 않는다.
as $$
  select team_id
  from members
  where user_id = auth.uid()
$$;

-- 4. Create Policy
create policy "Team members can update team members if they belong to the team."
on members
for all using (
  team_id in (
    select private.get_teams_for_authenticated_user()
  )
);

See also

Favorite site