149 lines
3.5 KiB
Python
149 lines
3.5 KiB
Python
"""
|
||
Supabase 安全知识
|
||
"""
|
||
|
||
from ..base import KnowledgeDocument, KnowledgeCategory
|
||
|
||
|
||
SUPABASE_SECURITY = KnowledgeDocument(
|
||
id="framework_supabase",
|
||
title="Supabase Security",
|
||
category=KnowledgeCategory.FRAMEWORK,
|
||
tags=["supabase", "postgresql", "rls", "auth", "baas"],
|
||
content="""
|
||
Supabase 是一个开源的Firebase替代品,安全性主要依赖于Row Level Security (RLS)。
|
||
|
||
## 核心安全机制
|
||
1. Row Level Security (RLS)
|
||
2. JWT认证
|
||
3. PostgreSQL权限系统
|
||
|
||
## 常见漏洞模式
|
||
|
||
### RLS未启用
|
||
```sql
|
||
-- 危险 - 表没有启用RLS
|
||
CREATE TABLE posts (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id UUID,
|
||
content TEXT
|
||
);
|
||
-- 任何人都可以访问所有数据!
|
||
|
||
-- 安全 - 启用RLS
|
||
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
|
||
|
||
CREATE POLICY "Users can only see their own posts"
|
||
ON posts FOR SELECT
|
||
USING (auth.uid() = user_id);
|
||
```
|
||
|
||
### RLS策略不完整
|
||
```sql
|
||
-- 危险 - 只有SELECT策略
|
||
CREATE POLICY "select_policy" ON posts FOR SELECT
|
||
USING (auth.uid() = user_id);
|
||
-- INSERT/UPDATE/DELETE没有策略,可能被绕过
|
||
|
||
-- 安全 - 完整的CRUD策略
|
||
CREATE POLICY "insert_policy" ON posts FOR INSERT
|
||
WITH CHECK (auth.uid() = user_id);
|
||
|
||
CREATE POLICY "update_policy" ON posts FOR UPDATE
|
||
USING (auth.uid() = user_id);
|
||
|
||
CREATE POLICY "delete_policy" ON posts FOR DELETE
|
||
USING (auth.uid() = user_id);
|
||
```
|
||
|
||
### 服务端密钥泄露
|
||
```javascript
|
||
// 危险 - 在前端使用service_role密钥
|
||
const supabase = createClient(url, 'service_role_key');
|
||
// service_role绕过RLS!
|
||
|
||
// 安全 - 前端只使用anon key
|
||
const supabase = createClient(url, 'anon_key');
|
||
```
|
||
|
||
### 不安全的函数
|
||
```sql
|
||
-- 危险 - SECURITY DEFINER函数
|
||
CREATE FUNCTION get_all_users()
|
||
RETURNS SETOF users
|
||
LANGUAGE sql
|
||
SECURITY DEFINER -- 以函数所有者权限执行
|
||
AS $$
|
||
SELECT * FROM users;
|
||
$$;
|
||
|
||
-- 安全 - 使用SECURITY INVOKER或添加检查
|
||
CREATE FUNCTION get_user_data(target_user_id UUID)
|
||
RETURNS SETOF users
|
||
LANGUAGE sql
|
||
SECURITY INVOKER
|
||
AS $$
|
||
SELECT * FROM users WHERE id = target_user_id;
|
||
$$;
|
||
```
|
||
|
||
### 存储桶权限
|
||
```sql
|
||
-- 危险 - 公开存储桶
|
||
INSERT INTO storage.buckets (id, name, public)
|
||
VALUES ('uploads', 'uploads', true);
|
||
-- 任何人都可以访问所有文件
|
||
|
||
-- 安全 - 私有存储桶 + RLS
|
||
INSERT INTO storage.buckets (id, name, public)
|
||
VALUES ('uploads', 'uploads', false);
|
||
|
||
CREATE POLICY "Users can access own files"
|
||
ON storage.objects FOR SELECT
|
||
USING (auth.uid()::text = (storage.foldername(name))[1]);
|
||
```
|
||
|
||
### JWT验证绕过
|
||
```javascript
|
||
// 危险 - 不验证JWT
|
||
const { data } = await supabase
|
||
.from('posts')
|
||
.select('*')
|
||
.eq('user_id', userIdFromRequest); // 用户可以伪造
|
||
|
||
// 安全 - 使用auth.uid()
|
||
// RLS策略中使用auth.uid()自动从JWT获取用户ID
|
||
```
|
||
|
||
### Edge Functions安全
|
||
```typescript
|
||
// 危险 - 不验证请求来源
|
||
Deno.serve(async (req) => {
|
||
const { userId } = await req.json();
|
||
// 直接使用用户提供的userId
|
||
});
|
||
|
||
// 安全 - 从JWT获取用户
|
||
import { createClient } from '@supabase/supabase-js';
|
||
|
||
Deno.serve(async (req) => {
|
||
const authHeader = req.headers.get('Authorization');
|
||
const supabase = createClient(url, anonKey, {
|
||
global: { headers: { Authorization: authHeader } }
|
||
});
|
||
const { data: { user } } = await supabase.auth.getUser();
|
||
// 使用验证过的user.id
|
||
});
|
||
```
|
||
|
||
## 安全检查清单
|
||
1. 所有表都启用了RLS
|
||
2. 每个表都有完整的CRUD策略
|
||
3. 前端只使用anon key
|
||
4. service_role key只在服务端使用
|
||
5. 存储桶有适当的访问策略
|
||
6. 函数使用SECURITY INVOKER
|
||
7. Edge Functions验证JWT
|
||
""",
|
||
)
|