å¿«éä½éª
é 读å建议å äºè§£ PG æ¨¡å¼æ¦è¿°ï¼æç¡® PostgreSQLã身份认è¯ãäºåå¨åæé模åçå ³ç³»ã
æ¬æä»¥ä¸ä¸ªæå°å¾ ååºç¨ä¸ºä¾ï¼æ¼ç¤º CloudBase PostgreSQL æ°æ®åºä¸ä»å»ºè¡¨ â å¯ç¨ RLS â ææ â åçç¥ â SDK è°ç¨ç宿´æµç¨ï¼å¸®ä½ å¨ 5ï½10 åéå è·éä¸ä¸ªå®å ¨çå¤ç¨æ·æ°æ®è®¿é®é¾è·¯ã
åç½®åå¤â
- å·² å建 PG 模å¼çäºå¼åç¯å¢ï¼å¹¶è®°å½ï¼
- ç¯å¢ IDï¼ä¾å¦
pg-test-3gxmdbdb580ecfd1ï¼ - Publishable Keyï¼å端使ç¨ï¼å¯¹åº
anonè§è²ï¼ - API Keyï¼å端使ç¨ï¼å¯¹åº
service_roleï¼ä¸¥ç¦æ´é²å端ï¼
- ç¯å¢ IDï¼ä¾å¦
- ä¸ç§è§è²ä¸ Key è¯¦è§ PGï¼èº«ä»½è®¤è¯ï¼å屿鿍¡åè¯¦è§ æ¶æä¸æéæ¨¡å
Publishable Key / API Key å¯å¨æ§å¶å°åå»ºï¼æéè¿ YUNAPI ç CreateApiKey å建ï¼ã
ç¬¬ä¸æ¥ï¼å»ºè¡¨â
å建ä¸å¼ todos 表ãå¯ä»¥ä½¿ç¨ SQL ç¼è¾å¨ï¼æ§å¶å° â æ°æ®åº â SQL ç¼è¾å¨ï¼ç´æ¥æ§è¡ï¼
CREATE TABLE public.todos (
id bigserial PRIMARY KEY,
title varchar(255) NOT NULL,
is_completed boolean NOT NULL DEFAULT false,
-- â owner_id èªå¨ä» JWT 读åå½åç¨æ· ID
owner_id varchar(64) NOT NULL
DEFAULT (current_setting('request.jwt.claims', true)::json->>'sub'),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX idx_todos_owner_id ON public.todos(owner_id);
å ³é®ï¼
owner_idçé»è®¤å¼ç»å® JWTsubï¼å端æ éä¼ å ¥owner_idï¼ä»æºå¤´æç»èº«ä»½ä¼ªé ã
ç¬¬äºæ¥ï¼å¯ç¨ RLSâ
ALTER TABLE public.todos ENABLE ROW LEVEL SECURITY;
å¯ç¨ RLS åï¼è¥æªé
ç½®ä»»ä½ Policyï¼ææé service_role ç¨æ·é½æ æ³è®¿é®ä»»ä½æ°æ®ï¼é»è®¤æç»ï¼ãç»§ç»ä¸ä¸æ¥ã
ç¬¬ä¸æ¥ï¼æäºè¡¨çº§æéï¼GRANTï¼â
表级 GRANT æ§å¶æ¯ç§è§è²è½å¯¹è¡¨æ§è¡åªäºç±»åçæä½ï¼
-- å¿åç¨æ·ï¼ä¸ç»ä»»ä½æéï¼æªç»å½ä¸è½æä½ä¸ªäººå¾
åï¼
-- ç»å½ç¨æ·ï¼å¯è¯»å¯åèªå·±çå¾
å
GRANT SELECT, INSERT, UPDATE, DELETE ON public.todos TO authenticated;
GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO authenticated;
-- 管çåï¼å
¨é¨æé
GRANT ALL ON public.todos TO service_role;
GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO service_role;
ç¬¬åæ¥ï¼å建 RLS çç¥ï¼Policyï¼â
è¡çº§ Policy æ§å¶æ¯ç§è§è²è½æä½åªäºå ·ä½çè¡ï¼
-- SELECTï¼ç»å½ç¨æ·ä»
è½è¯»èªå·±çå¾
å
CREATE POLICY todos_select_own ON public.todos
FOR SELECT TO authenticated
USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'sub'));
-- INSERTï¼ç»å½ç¨æ·ä»
è½åå
¥ owner_id = èªå·± çè®°å½
CREATE POLICY todos_insert_own ON public.todos
FOR INSERT TO authenticated
WITH CHECK (owner_id = (current_setting('request.jwt.claims', true)::json->>'sub'));
-- UPDATEï¼ç»å½ç¨æ·ä»
è½æ´æ°èªå·±çè®°å½ï¼ä¸ä¸è½æ owner_id æ¹æå«äºº
CREATE POLICY todos_update_own ON public.todos
FOR UPDATE TO authenticated
USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'sub'))
WITH CHECK (owner_id = (current_setting('request.jwt.claims', true)::json->>'sub'));
-- DELETEï¼ç»å½ç¨æ·ä»
è½å èªå·±çè®°å½
CREATE POLICY todos_delete_own ON public.todos
FOR DELETE TO authenticated
USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'sub'));
ä¸è¿° SQL å¯éè¿äº API ExecutePGSql æ§è¡ï¼éå CI/CDï¼ã注æ é¨å DDL å¨ API è·¯å¾ä¸éè¦ç¨ DO LANGUAGE plpgsql $$ BEGIN EXECUTE '...'; END $$ å
è£
åéè¯ï¼è¿æ¯å 为å¿å代ç åä¾èµ BEGIN å卿 EXECUTEï¼è¯¦è§ æ¶æä¸æéæ¨¡å - ExecutePGSqlã
ç¬¬äºæ¥ï¼éè¿ SDK è¯»åæ°æ®â
å®è£ â
npm install @cloudbase/js-sdk
使ç¨â
import cloudbase from '@cloudbase/js-sdk';
// ä¹å¯ä»¥ä¼ å
¥ accessKey: '<Publishable Key>'ï¼è¿æ ·æªæ¾å¼ç»å½æ¶å³ä»¥ anon 身份访é®
const app = cloudbase.init({ env: '<äºå¼åç¯å¢ ID>' });
const auth = app.auth;
// 1. ç»å½ï¼å¿å / è´¦å·å¯ç / ç¬¬ä¸æ¹åå¯ï¼æ¤å¤ä»¥å¿å为ä¾ï¼
// å¿åç»å½å JWT ä¸ role=anon ä½å¸¦çå® subï¼å¯ä½ä¸º owner_id å½å±å段使ç¨
await auth.signInAnonymously();
// 2. è®¿é® PG æ°æ®åº
const db = app.rdb();
// æ¥è¯¢ï¼RLS èªå¨è¿æ»¤ï¼åªè¿åå½åç¨æ·çå¾
å
const { data, error } = await db
.from('todos')
.select('*')
.eq('is_completed', false);
// æ°å¢ï¼owner_id ç±æ°æ®åºé»è®¤å¼èªå¨å¡«å
为 JWT ç sub
await db.from('todos').insert({ title: 'åä¸ç¯ææ¡£' });
// æ´æ°ï¼RLS åªå
许æ¹èªå·±ç
await db.from('todos').update({ is_completed: true }).eq('id', 1);
// å é¤ï¼RLS åªå
许å èªå·±ç
await db.from('todos').delete().eq('id', 1);
app.authæ¯å±æ§ï¼ä¸æ¯æ¹æ³ï¼ä¸è¦åapp.auth()ï¼- è´¦å·å¯ç ç»å½ä½¿ç¨
auth.signInWithPassword({ username, password }) - è¯¦ç» API è§ JS SDK - PostgreSQL æ°æ®åº ä¸ JS SDK - 认è¯
ç¬¬å æ¥ï¼ç´æ¥ä½¿ç¨ REST APIï¼å¯éï¼â
è¥æ SDK åºæ¯ï¼å¯ç´æ¥è° PostgREST RESTful APIï¼
# ç»å½
curl -X POST "https://<envId>.api.tcloudbasegateway.com/auth/v1/signin/anonymously" \
-H "Content-Type: application/json" \
-d '{}'
# ååºï¼{ "access_token": "eyJhbG...", ... }
# æ¥è¯¢
curl "https://<envId>.api.tcloudbasegateway.com/v1/rdb/rest/todos?select=*&is_completed=eq.false" \
-H "Authorization: Bearer <access_token>"
# æ°å¢ï¼owner_id çç¥ï¼ç± DEFAULT èªå¨å¡«å
ï¼
curl -X POST "https://<envId>.api.tcloudbasegateway.com/v1/rdb/rest/todos" \
-H "Authorization: Bearer <access_token>" \
-H "Prefer: return=representation" \
-H "Content-Type: application/json" \
-d '{"title":"åä¸ç¯ææ¡£"}'
éªè¯ RLS æ¯å¦çæâ
- æªç»å½è®¿é®ï¼ä¸å¸¦ Authorization æç¨
anon身份è°ç¨ï¼â æ æéï¼è¿å空æ°ç»æ 403 - ç»å½åè®¿é® â ä» è¿åèªå·±çæ°æ®
- 伪é owner_idï¼åç«¯æ¶æä¼ å
¥
"owner_id": "other-user"ï¼â 被WITH CHECKæç» - å°è¯è¯»å
¶ä»ç¨æ·çå¾
åï¼ä¾å¦
?owner_id=eq.other-userï¼â å³ä½¿å¸¦è¿æ»¤æ¡ä»¶ï¼USINGçç¥ä»ç¶åªè¿åèªå·±çæ°æ®
常è§é·é±â
- å¯ç¨äº RLS 使²¡å Policy â ææé
service_role请æ±é½è¢«æç» - åäº Policy ä½å¿è®° GRANT â 表级æéä¸éè¿
- UPDATE åªåäº
USINGï¼æ²¡åWITH CHECKâ ç¨æ·å¯ä»¥æowner_idæ¹æå«äººï¼çªåæ°æ® - å¨å端代ç ä¸ä½¿ç¨
API Keyâ çäºæææ°æ®å®å ¨æ´é²ï¼service_roleBYPASSRLSï¼ - 使ç¨
serial/bigserial主é®å¿è®°æäºSEQUENCEæé â INSERT æ¥æéé误 - DDL éè¿
ExecutePGSqlæ¶é¨åè¯å¥ç´æ¥æ¥é â 失败æ¶ç¨DO LANGUAGE plpgsql $$ BEGIN EXECUTE '...'; END $$å è£ éè¯
ä¸ä¸æ¥â
- æ¡æ¶å¿«éå ¥é¨ï¼
- æ·±å
¥é
读ï¼
- PGï¼èº«ä»½è®¤è¯ï¼å«ä¸è§è² + JWTï¼
- PG 模å¼äºåå¨
- æ¶æä¸æéæ¨¡å
- åºç¡æé管ç
- PostgREST RESTful API