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

Spring Boot R2DBC如何在每次查询前设置数据库会话变量?

在R2DBC Spring Boot应用中自动设置会话变量实现行级安全

核心思路

无需修改现有业务代码,通过R2DBC连接装饰器拦截连接创建流程,自动前置会话变量设置SQL;同时借助Reactor Context传递Web会话中的行级安全参数,全程保持非阻塞特性。

实现步骤

  • 步骤1:将Web会话参数存入Reactor Context
    在Web过滤器中,提取Web会话里的行级安全参数(比如当前用户ID),存入Reactor Context,确保后续R2DBC操作能异步获取该值:

    @Component
    public class RowSecurityContextFilter implements WebFilter {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
            Mono<String> userIdMono = exchange.getSession()
                    .map(session -> session.getAttribute("userId"))
                    .cast(String.class)
                    .defaultIfEmpty("anonymous");
    
            return chain.filter(exchange)
                    .contextWrite(ctx -> userIdMono.map(userId -> ctx.put("ROW_SECURITY_USER_ID", userId))
                            .blockOptional()
                            .orElse(ctx));
        }
    }
    
  • 步骤2:装饰ConnectionFactory自动执行SET语句
    实现ConnectionFactoryDecorator接口,拦截连接创建过程,每次获取到新连接后,先执行会话变量设置的参数化SQL,再将连接返回给业务逻辑使用:

    @Component
    public class RowSecurityConnectionDecorator implements ConnectionFactoryDecorator {
        @Override
        public ConnectionFactory decorate(ConnectionFactory originalFactory) {
            return new ConnectionFactory() {
                @Override
                public Publisher<? extends Connection> create() {
                    return originalFactory.create()
                            .flatMap(connection -> Mono.deferContextual(contextView -> {
                                String userId = contextView.getOrDefault("ROW_SECURITY_USER_ID", "anonymous");
                                // 使用参数化查询避免SQL注入风险
                                return connection.createStatement("SET SESSION row_security_user_id = $1")
                                        .bind(0, userId)
                                        .execute()
                                        .then(Mono.just(connection));
                            }));
                }
    
                @Override
                public ConnectionFactoryMetadata getMetadata() {
                    return originalFactory.getMetadata();
                }
            };
        }
    }
    
  • 步骤3:确保装饰器生效
    Spring Boot会自动扫描并应用ConnectionFactoryDecorator类型的Bean,无需额外配置。如果是自定义配置场景,也可以手动声明Bean:

    @Configuration
    public class R2dbcConfig {
        @Bean
        public ConnectionFactoryDecorator rowSecurityConnectionDecorator() {
            return new RowSecurityConnectionDecorator();
        }
    }
    

关键注意事项

  • SQL注入防护:必须使用参数化绑定(如示例中的$1和bind(0, userId))生成SET语句,绝对禁止字符串拼接,避免恶意参数注入。
  • 上下文传递有效性:业务层的所有R2DBC操作(仓库返回的Mono/Flux)必须保持在原始Reactor Context中,禁止使用block()等阻塞操作破坏上下文传递链。
  • 连接复用处理:若使用连接池,每次获取连接时都会重新执行SET语句,覆盖连接复用前的旧变量值,确保行级安全参数始终对应当前请求的用户。

替代方案:DatabaseClient拦截器

如果仅需拦截特定DatabaseClient的查询,可自定义执行器,在每次查询前自动插入SET语句:

@Bean
public DatabaseClient secureDatabaseClient(ConnectionFactory connectionFactory) {
    return DatabaseClient.builder()
            .connectionFactory(connectionFactory)
            .executeFunction((conn, stmt) -> Mono.deferContextual(ctxView -> {
                String userId = ctxView.getOrDefault("ROW_SECURITY_USER_ID", "anonymous");
                return conn.createStatement("SET SESSION row_security_user_id = $1")
                        .bind(0, userId)
                        .execute()
                        .then(stmt.execute());
            }))
            .build();
}

业务代码中使用此secureDatabaseClient执行的所有查询,都会自动前置会话变量设置逻辑。

内容的提问来源于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 16:55:20