拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Java 程序员第 47 阶段04:多租户知识库隔离设计,适配 SaaS 系统大模型架构,共享库加行级隔离 Row-Level Security 的实现

1. 为什么选共享库 RLS当你的租户是上万个中小客户而非几个大客户时独立库或独立 Schema 的实例/连接成本都受不了。共享库 行级隔离RLS是高密度 SaaS 的标配分层见图 figure_04_1。它的优势是单实例承载海量租户风险是任何一次漏写where tenant_id都可能串租户。所以必须让数据库在最后一米兜底。2. 数据库层 RLS 配置PostgreSQL 的ROW LEVEL SECURITY让策略绑定到表任何查询无论是否带 where都自动附加租户条件示意见图 figure_04_2-- 开启行级安全ALTERTABLEkb_documentENABLEROWLEVELSECURITY;ALTERTABLEkb_chunkENABLEROWLEVELSECURITY;-- 策略只能看见自己租户的行CREATEPOLICY kb_doc_tenantONkb_documentUSING(tenant_idcurrent_setting(app.tenant,true)::text);CREATEPOLICY kb_chunk_tenantONkb_chunkUSING(tenant_idcurrent_setting(app.tenant,true)::text);current_setting(app.tenant, true)的第二个参数true表示缺失时返回 NULL配合 NOT NULL 约束可让越权查询直接返回空。3. 应用层绑定会话变量每次拿到数据库连接先执行SET app.tenant ?把当前tenantId写进会话绑定流程见图 figure_04_3。用DataSource包装或 MyBatis 拦截器统一做publicclassTenantConnectionWrapper{publicstaticvoidbindTenant(Connectionconn,StringtenantId)throwsSQLException{try(Statementstconn.createStatement()){st.execute(SET app.tenant sanitize(tenantId));}}privatestaticStringsanitize(Stringt){if(!t.matches([a-zA-Z0-9_-]{1,64}))thrownewIllegalArgumentException(bad tenant);returnt;}}注意tenantId必须白名单校验绝不能拼未经检查的字符串否则引入 SQL 注入。4. MyBatis 拦截器自动附 tenant_id业务 SQL 里不必每次手写tenant_id用 MyBatisInterceptor在WHERE后自动追加杜绝漏写Intercepts({Signature(typeStatementHandler.class,methodprepare,args{Connection.class,Integer.class})})publicclassTenantSqlInterceptorimplementsInterceptor{OverridepublicObjectintercept(Invocationinv)throwsThrowable{StatementHandlersh(StatementHandler)inv.getTarget();BoundSqlsqlsh.getBoundSql();StringnewSqlappendTenant(sql.getSql(),TenantContext.get());// 通过反射改写 BoundSql.sqlreturninv.proceed();}}防线作用是否可绕过应用层显式 tenant_id正常路径代码 bug 可能漏MyBatis 拦截器自动补条件仍需正确配置数据库 RLS最后一米兜底不可绕过5. 兜底与防绕过的测试三层防线里RLS 是不可绕过的那一层。必须写集成测试验证用 tenant-a 的会话执行不带 where的查询断言结果集只含 tenant-a。同时压测连接池确认SET app.tenant在每次借出连接时都被重置避免线程复用串租户。6. 小结共享库 RLS 是高密度 SaaS 知识库的经济解法用数据库层策略做兜底应用层做主动过滤两者叠加把串租户风险压到最低。下一篇我们看另一种折中——独立 Schema 与动态数据源。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门