You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于R2DBC的Postgres行级安全:AbstractRoutingDataSource替代方案问询

R2DBC实现租户级Postgres行级安全(替代AbstractRoutingDataSource的响应式方案)

R2DBC生态中没有直接对应JDBC里AbstractRoutingDataSource的类,因为两者核心模型不同(JDBC依赖DataSource,R2DBC核心是ConnectionFactory)。针对Postgres行级安全(RLS)场景,通常无需切换数据源,而是在创建数据库连接时动态设置租户上下文变量,具体实现方案如下:

1. 用Reactor Context传递租户ID

响应式场景下不能用ThreadLocal存储租户信息,需借助Reactor的Context传递:

public class TenantContext {
    public static final String TENANT_ID_KEY = "tenantId";

    public static Mono<String> getTenantId() {
        return Mono.deferContextual(ctx -> Mono.justOrEmpty(ctx.getOrDefault(TENANT_ID_KEY, null)));
    }
}

2. 实现自定义ConnectionFactory包装类

包装原生Postgres ConnectionFactory,在创建连接时自动设置RLS租户变量:

public class TenantAwareConnectionFactory implements ConnectionFactory {
    private final ConnectionFactory targetConnectionFactory;

    public TenantAwareConnectionFactory(ConnectionFactory targetConnectionFactory) {
        this.targetConnectionFactory = targetConnectionFactory;
    }

    @Override
    public Publisher<? extends Connection> create() {
        return TenantContext.getTenantId()
                .flatMap(tenantId -> targetConnectionFactory.create()
                        .flatMap(connection -> {
                            // 参数化设置RLS租户变量,避免SQL注入
                            return connection.createStatement("SET app.current_tenant = $1;")
                                    .bind(0, tenantId)
                                    .execute()
                                    .then(Mono.just(connection));
                        }));
    }

    @Override
    public ConnectionFactoryMetadata getMetadata() {
        return targetConnectionFactory.getMetadata();
    }
}

注:app.current_tenant是你在Postgres中为RLS定义的会话变量,需提前在数据库中配置对应的RLS策略

3. 配置自定义ConnectionFactory

在Spring Boot中替换默认的ConnectionFactory:

@Configuration
public class R2dbcConfig {
    @Bean
    public ConnectionFactory connectionFactory(ConnectionFactoryProperties properties) {
        PostgresConnectionConfiguration config = PostgresConnectionConfiguration.builder()
                .host(properties.getHost())
                .port(properties.getPort())
                .database(properties.getDatabase())
                .username(properties.getUsername())
                .password(properties.getPassword())
                .build();
        PostgresConnectionFactory originalFactory = new PostgresConnectionFactory(config);
        return new TenantAwareConnectionFactory(originalFactory);
    }
}

4. 在请求链路中注入租户ID

通过WebFilter将请求中的租户ID(比如从请求头X-Tenant-ID获取)存入Reactor Context:

@Component
public class TenantWebFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        String tenantId = exchange.getRequest().getHeaders().getFirst("X-Tenant-ID");
        return chain.filter(exchange)
                .contextWrite(ctx -> ctx.put(TenantContext.TENANT_ID_KEY, tenantId));
    }
}

扩展:多租户独立数据库场景

如果你的场景是每个租户对应独立数据库,需要动态切换ConnectionFactory,可以修改自定义ConnectionFactory实现:

public class MultiTenantRoutingConnectionFactory implements ConnectionFactory {
    private final Map<String, ConnectionFactory> tenantConnectionFactories;
    private final ConnectionFactory defaultConnectionFactory;

    public MultiTenantRoutingConnectionFactory(Map<String, ConnectionFactory> tenantConnectionFactories, 
                                               ConnectionFactory defaultConnectionFactory) {
        this.tenantConnectionFactories = tenantConnectionFactories;
        this.defaultConnectionFactory = defaultConnectionFactory;
    }

    @Override
    public Publisher<? extends Connection> create() {
        return TenantContext.getTenantId()
                .map(tenantId -> tenantConnectionFactories.getOrDefault(tenantId, defaultConnectionFactory))
                .flatMap(ConnectionFactory::create);
    }

    @Override
    public ConnectionFactoryMetadata getMetadata() {
        return defaultConnectionFactory.getMetadata();
    }
}

内容的提问来源于stack exchange,提问作者kylie.zoltan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 15:40:24