基于RLS的微服务租户隔离(Tenant Isolation)解决方案

2020-07-02 00:00:00 数据 微服 数据库 访问 租户

现在的软件设计中,微服务的解决方案越来越流行, 每个微服务负责一个特定的业务,大量的微服务相互协作通讯组成了一个完整的系统。 每个微服务通常都有自己管理的业务数据和对应的数据库。 通常每个公司(或者称租户)的数据在数据库中的存储有两种设计的方案:

  1. 每个租户有一个自己的 Schema。每个Schema里面有一组相同的和这个业务有关的表。

2. 所有的租户共享一个Schema。每个租户的数据都存储在同一张表中, 然后每一张业务表都有一列(通常这一列叫做 TenantID)来表示当前的行数据是属于那个租户。

个方案的好处, 就是各个租户之间的数据是完全的隔离, 一个公司的用户只能访问自己数据库里面的数据, 实现的原理通常是根据当前的用户所在的公司, 获取对应的数据库的Connection,该Connection只能对自己公司的 Schema进行操作。缺点是数据库的资源会消耗的比较多, 而且不易统一的升级管理, 需要对数据库逐一的进行升级,投入的维护人力会比较大。 通常这种解决方案适合数据量大, 对数据安全要求高的大公司。

第二种方案的优势和种方案的缺点相对, 就是数据库是统一的升级和维护, 如果大量的租户是数据量不大的小公司, 那么把它们的数据放在同一张表里面无疑是非常合适的。 缺点也很明显, 那就是使用同一个数据库连接,如果不在应用层做额外的处理, 理论上登陆用户能访问到不同租户的数据。

种方案是传统的租户数据的存储方案, 在微服务流行之前, 广泛的存在于各个面向企业的应用程序软件中。 第二种方案, 则随着微服务的流行, 变得越来越普遍, 因为每个微服务通常是由一个小团队来负责, 他们通常都没有足够的人力来为每一个客户公司升级维护一套 Schema。

问题来了, 如果是用 Share Schema这样的多租户数据存储方案, 怎么如何来保证数据访问的安全性?

一种方式就是在应用层来做数据访问的控制。 在业界流行的基于Spring-Boot的微服务的设计中, 通常使用两种数据库访问框架, 一个是 Mybatis, 这个在国内互联网公司比较的流行。另外一个是JPA, 这个在国外用的比较多。 JPA作为一个标准它有两个比较流行的实现一个是 hibernate, 一个是 EclipseLink。

可惜的是, Mybatis自己并没有针对Share Schema的多租户数据库在框架级别提供解决方案。 也就是说, 每个开发人员必须自己保证自己所写的sql语句包含了对当前公司Tenant ID的过滤, 这对开发人员的要求就相应的比较高。 当然也有一些开源项目, 在 mybatis的基础之上, 增加了对 Share Schema的支持, 比如说 mybatis-plus(mp.baomidou.com/guide/t), 但是似乎他自身的限制还比较多。

JPA的标准包含了对 Share Schema的数据库表的支持, 它把存储TenantID的列定义为Tenant Discriminator。 JPA的实现需要将这个 Tenant Discriminator 放在终生成的SQL语句中, 这样保证不同的用户, 使用相同的JQL, 访问的只能是自己公司的数据。 可惜的是, 只有EclipseLink目前对这个规范有比较好的支持, 流行广的Hibernate要到7.0版本之后才能对此有支持(hibernate.atlassian.net)。

这样一来, 似乎在应用程序的数据库访问层来做租户数据隔离变成了一个不易完成的任务。

幸运的是, 很多的数据库(可惜除了 mysql), 提供了一种新的数据访问控制, 就是本文要介绍的 RLS(Row Level Security)。 基于RLS, 微服务能够轻易的就实现租户的数据隔离。

以下以PostgreSQL为例,我们假设在public schema下面有一张商品表,为了简化, 这张表现在只有ID,NAME, TENANT_ID, 创建时间和更新时间这些列。

-- create table
CREATE TABLE IF NOT EXISTS PRODUCT(
    ID UUID  PRIMARY KEY,
    NAME VARCHAR(256) UNIQUE NOT NULL,
    TENANT_ID VARCHAR(256) NOT NULL,
    CREATED_DATETIME TIMESTAMP NOT NULL,
    UPDATED_DATETIME TIMESTAMP NOT NULL
);

相关文章