코딩/React

Supabase와 PostGreSQL의 Row Level Security 살펴보기

드리프트 2022. 2. 20. 17:57
728x170

 

안녕하세요?

 

지난 시간에 깜빡했던 PostgreSQL DB의 Row Level Security에 대해 추가로 알아볼 게 있어서  잠깐 글을 쓰게 되었습니다.

 

CREATE TABLE posts (
  id bigint generated by default as identity primary key,
  user_id uuid references auth.users not null,
  user_email text,
  title text,
  content text,
  inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);

alter table posts enable row level security;

create policy "Individuals can create posts." on posts for
    insert with check (auth.uid() = user_id);

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

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

create policy "Posts are public." on posts for
    select using ( true );

정책(Policy)에서 마지막에 보시면 "Posts are public." 부분이 있습니다.

 

그리고 그다음에 보시면 select using ( true );라고 되어 있습니다.

 

즉, SQL 명령어 select를 사용해서 posts에 접근하는 걸 허용(true)한다는 뜻인데요.

 

이 policy를 켜 두게 되면 모든 사용자가 posts 테이블의 모든 데이터를 select 할 수 있게 됩니다.

 

즉, 볼 수는 있다는 뜻이죠.

 

만약, select using ( false); 로 바꾸게 되면 어떻게 되는지 한 번 볼까요?

 

우리가 만들었던 Posts List 페이지를 대상으로 알아보겠습니다.

 

지난 시간에 로그인했던 아이디는 cpro95@daum.net 계정이었습니다.

 

만약, 다른 계정으로 로그인했을 때는 Posts List 페이지가 어떻게 되는지 살펴보겠습니다.

 

먼저, supabase 대시보드에서 select using ( false );로 바꾸는 방법에 대해 알아보겠습니다.

 

일단 Authenticaion > Policies로 가시면 우리가 만들었던 Policies가 나옵니다.

 

위 그림에서 볼 수 있듯이 SQL 명령어 중에 SELECT 부분이 첫 번째에 있습니다.

 

이제 마우스를 첫 번째 행에 갖다 되면 Edit와 Delete가 뜨는데요.

 

Edit 버튼을 눌러봅시다.

 

그러면 true 가 보일 겁니다.

 

이걸 false로 바꾸고 오른쪽 아래 Review 버튼을 눌러줍시다.

 

그러면 아래와 같이 나오는데요.

 

false로 잘 적용되었네요. 이제 Save policy 버튼을 눌러 정책을 바꿉시다.

 

그럼 이제 https://localhost:3000/posts 페이지로 가서 테스트해볼까요?

 

아래 스크린숏은 로그인 아이디가 cpro95@gmail.com입니다.

 

근데 글은 cpro95@daum.net 아이디로 작성됐습니다.

 

그런데 select using ( true );로 정책이 설정되어 있을 때는 아래처럼 다른 사용자도 글을 볼 수 있습니다.

 

이제 SELECT 정책을 false로 바꾸고 다시 posts 경로에 접근해 볼까요?

 

 

역시나 글이 안 보입니다.

 

그럼 cpro95@daum.net 아이디로 다시 로그인해볼까요?

 

한번 해보시면 역시나 안 보일 겁니다.

 

select using ( false )로 바꾸면 아무도 SELECT를 못 보는 뜻입니다.

 

그럼 어떻게 해야 할까요?

 

바로 INSERT, UPDATE, DELETE 정책을 참조하면 됩니다.

 

SQL Query 만들 때 아래처럼 하시면 됩니다.

create policy "Posts are public." on posts for
    select using (auth.uid() = user_id);

즉, SELECT SQL 명령어도 uid가 user_id와 맞을 때만 보여준다는 뜻입니다.

 

그럼 한번 만들었던 정책(Policy)을 supabase 대시보드에서는 어떻게 바꿀까요?

 

위와 같이 되어 있던걸 아래 그림처럼 바꾸면 됩니다.

 

 

(uid() = user_id) 부분은 UPDATE 부분에서 가져왔습니다.

 

SQL Query에서는 auth.uid() = user_id라고 했지만, 대시보드에서는 그냥 uid()라고 쓰시면 됩니다.

 

그리고 이름도 제대로 바꿨습니다.

 

이제 테스트해볼까요?

 

 

역시나 예상했던 데로 잘 나옵니다.

 

상세 페이지로 들어 가보면 기존에 서버 사이드로 작성했으면 post가 보이지 않을 겁니다.

 

상세 페이지를 클라이언트 사이드 방식으로 작성해야만 post가 보이는데요.

 

왜냐하면, 서버 사이드로 작성된 코드를 보시면 getServerSideProps 함수에서 user와 post를 둘다 await 즉, 비동기로 불러오기 때문에 post를 불러올 때 user가 아직 불어오지 않았을 경우에 PostGreSQL의 Row Level Security에 의해 접근이 아예 안되게 됩니다.

 

그럼 Post List 페이지는 왜 되냐하면 Posts List 페이지는 클라이언트 사이드에서 fetchPosts를 했기 때문입니다.

 

그럼 서버 사이드에서 실행되게 할려면 어떻게 해야 할까요?

 

세션 쿠키를 이용해서 토큰을 전달해 주면 됩니다.

 

npm i cookie

 

일단 cookie 패키지를 설치해주고요.

 

  const token = cookie.parse(req.headers.cookie)["sb:token"];
  const user2 = cookie.parse(req.headers.cookie)["user"];
  
  supabase.auth.session = () => ({
    token_type: "bearer",
    user: user2,
    access_token: token,
  });

  // returned supabase data is basically array, so post is array type
  // but .single() function will return only one object, that is not array type.
  let { data: post, error } = await supabase
    .from("posts")
    .select("*")
    .eq("id", Number(params?.id))
    .limit(1)
    .single();

  if (!post) {
    console.log(error);
  } else {
    // console.log(post);
  }

위 코드와 같이 getServerSideProps 부분에서 token 부분을 추가해 줍니다.

 

이렇게 되면 쿠키에 있던 토큰을 이용해서 세션을 복구한 경우가 되서 아래 supabase.from.select가 실행될 수 있습니다.

 

지금까지 PostGreSQL의 Row Level Security(RLS) 부분을 좀 더 깊게 알아봤는데요.

 

어떤 방식을 적용할지는 온전히 프로그래머 마음이기 때문에 참고하시길 바랍니다.

 

 

그리드형