Supabase Row Level Security 完整設定指南
10 分鐘閱讀 1,050 字
Supabase Row Level Security 完整設定指南
Row Level Security(RLS)是 PostgreSQL 提供的資料列層級安全機制,也是 Supabase 安全模型的核心。正確設定 RLS,能確保每個使用者只能存取自己有權限的資料,即使直接透過 API 存取資料庫也安全無虞。
為什麼 RLS 至關重要
Supabase 的 API Key 是公開的(anon key 設計上就是要放在前端),因此安全的唯一保障就是 RLS。如果你在 Supabase 開了一張表但沒有啟用 RLS,任何人都能用 anon key 讀寫所有資料。
-- 危險:沒有 RLS,所有人都能存取
SELECT * FROM users; -- 讀取所有使用者資料!
-- 啟用 RLS 後,必須符合 Policy 才能存取
ALTER TABLE users ENABLE ROW LEVEL SECURITY;基本 Policy 語法
CREATE POLICY policy_name
ON table_name
FOR operation -- ALL | SELECT | INSERT | UPDATE | DELETE
TO role -- authenticated | anon | service_role
USING (condition)
WITH CHECK (condition);常見 RLS 模式
1. 使用者只能存取自己的資料
CREATE TABLE profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
username TEXT UNIQUE NOT NULL,
bio TEXT,
avatar_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
-- 任何人都能讀取 profiles(公開資料)
CREATE POLICY "Profiles are publicly readable"
ON profiles FOR SELECT
TO public
USING (true);
-- 只有本人能更新自己的 profile
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE
TO authenticated
USING (auth.uid() = id)
WITH CHECK (auth.uid() = id);2. 多租戶資料隔離
CREATE TABLE documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
title TEXT NOT NULL,
content TEXT,
is_public BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
-- 讀取:只能看自己的或公開的文件
CREATE POLICY "Read own or public documents"
ON documents FOR SELECT
TO authenticated
USING (user_id = auth.uid() OR is_public = true);
-- 插入:只能插入 user_id 為自己的資料
CREATE POLICY "Insert own documents"
ON documents FOR INSERT
TO authenticated
WITH CHECK (user_id = auth.uid());
-- 更新與刪除類似設定...3. 團隊/組織層級的權限
CREATE TABLE org_members (
org_id UUID NOT NULL,
user_id UUID NOT NULL REFERENCES auth.users(id),
role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member')),
PRIMARY KEY (org_id, user_id)
);
CREATE TABLE org_projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
name TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE org_projects ENABLE ROW LEVEL SECURITY;
-- 組織成員才能讀取組織的專案
CREATE POLICY "Org members can read projects"
ON org_projects FOR SELECT
TO authenticated
USING (
org_id IN (
SELECT org_id FROM org_members
WHERE user_id = auth.uid()
)
);
-- 只有 owner 或 admin 能建立專案
CREATE POLICY "Org admins can create projects"
ON org_projects FOR INSERT
TO authenticated
WITH CHECK (
org_id IN (
SELECT org_id FROM org_members
WHERE user_id = auth.uid()
AND role IN ('owner', 'admin')
)
);使用自訂 JWT Claims 加速權限檢查
每次 RLS 查詢子查詢都有效能成本。可以透過 JWT 的自訂 Claims 避免頻繁的子查詢:
CREATE OR REPLACE FUNCTION auth.user_role()
RETURNS TEXT AS $$
SELECT COALESCE(
current_setting('request.jwt.claims', true)::json->>'user_role',
'user'
);
$$ LANGUAGE sql STABLE;
CREATE POLICY "Admins can read all data"
ON sensitive_data FOR SELECT
TO authenticated
USING (auth.user_role() = 'admin');除錯 RLS 問題
-- 查看現有 Policy
SELECT schemaname, tablename, policyname, cmd, qual
FROM pg_policies
WHERE schemaname = 'public';
-- 暫時以 service_role 繞過 RLS 測試
-- (只在除錯時使用,絕不在正式 API 中使用)在前端插入資料時,RLS 的 WITH CHECK 條件會自動驗證:
// 不需要手動傳入 user_id,Supabase 從 JWT 自動識別
const { data, error } = await supabase
.from('documents')
.insert({ title: 'My Doc', content: '...' })效能最佳化
複雜的 Policy 子查詢可能導致效能下降。解法:
- 建立適當索引:在
user_id、org_id等外鍵欄位加索引 - 使用 Security Definer 函式:封裝複雜邏輯,讓 PostgreSQL 快取執行計畫
- JWT Claims:將常用的角色資訊存入 JWT,避免每次查詢資料庫
-- 在外鍵欄位建立索引
CREATE INDEX idx_documents_user_id ON documents(user_id);
CREATE INDEX idx_org_members_user_id ON org_members(user_id);總結
RLS 是 Supabase 安全架構的基石。核心原則:所有表格預設啟用 RLS,然後按需求開放權限(Deny by default)。避免過度複雜的 Policy,必要時使用 Security Definer 函式和自訂 JWT Claims 優化查詢效能。
分享這篇文章