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

Spring Boot 2.7集成Oracle 12.2实现请求级代理用户连接问题

Spring Boot 2.7 实现Oracle请求级代理用户连接方案

核心需求

基于Spring Boot 2.7的Web应用对接Oracle 12.2,当前通过Tomcat JNDI配置schema owner账号的数据源,需要在每个HTTP请求级别动态使用Oracle代理用户连接数据库,适配Oracle Label Security(OLS)的权限控制,支持Oracle Thin驱动(无需OCI)。

解决方案思路

通过自定义DataSource包装JNDI数据源,结合ThreadLocal存储当前请求的代理用户名,在获取连接时动态创建Oracle代理连接;同时通过请求拦截器处理ThreadLocal的设置与清理,确保请求隔离。

步骤1:定义ThreadLocal存储代理用户名

用于在请求生命周期内传递当前需要使用的代理用户:

public class ProxyUserHolder {
    private static final ThreadLocal<String> PROXY_USER = new ThreadLocal<>();

    public static void setProxyUser(String username) {
        PROXY_USER.set(username);
    }

    public static String getProxyUser() {
        return PROXY_USER.get();
    }

    public static void clear() {
        PROXY_USER.remove();
    }
}

步骤2:实现请求拦截器,处理代理用户的传递与清理

从当前请求上下文(比如Spring Security的登录用户、请求参数/header)获取代理用户名,请求结束后清理ThreadLocal:

@Component
public class ProxyUserInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 根据实际场景获取代理用户名,示例从Spring Security认证信息中取
        String proxyUser = SecurityContextHolder.getContext().getAuthentication().getName();
        ProxyUserHolder.setProxyUser(proxyUser);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ProxyUserHolder.clear();
    }
}

注册拦截器:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private ProxyUserInterceptor proxyUserInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(proxyUserInterceptor).addPathPatterns("/**");
    }
}

步骤3:自定义代理DataSource,动态创建Oracle代理连接

包装原有的JNDI数据源,重写getConnection()方法,获取基础连接后转换为OracleConnection并创建代理连接:

@Component
@Primary
public class OracleProxyDataSource implements DataSource {

    @Autowired
    @Qualifier("dataSource")
    private DataSource targetDataSource;

    @Override
    public Connection getConnection() throws SQLException {
        String proxyUser = ProxyUserHolder.getProxyUser();
        if (proxyUser == null || proxyUser.isEmpty()) {
            // 无代理用户时直接返回原连接
            return targetDataSource.getConnection();
        }

        // 获取基础连接并转换为OracleConnection
        Connection conn = targetDataSource.getConnection();
        OracleConnection oracleConn = conn.unwrap(OracleConnection.class);

        // 使用Thin驱动创建代理连接
        Properties proxyProps = new Properties();
        proxyProps.put(OracleConnection.PROXY_USER_NAME, proxyUser);
        return oracleConn.createProxyConnection(OracleConnection.PROXY_TYPE_USER_NAME, proxyProps);
    }

    // 其余DataSource方法直接委托给targetDataSource
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return targetDataSource.getConnection(username, password);
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return targetDataSource.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return targetDataSource.isWrapperFor(iface);
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return targetDataSource.getParentLogger();
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return targetDataSource.getLogWriter();
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        targetDataSource.setLogWriter(out);
    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        targetDataSource.setLoginTimeout(seconds);
    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return targetDataSource.getLoginTimeout();
    }
}

步骤4:数据库端代理用户授权验证

确保代理用户(如scott)被授权允许代理目标用户(如user):

-- 授予scott代理user的权限
GRANT CONNECT THROUGH scott TO user;

连接测试:

connect user[scott]/tiger

关键说明

  • Oracle Thin驱动从11g开始支持代理用户认证,无需依赖OCI
  • 通过ThreadLocal实现请求级代理用户传递,避免多数据源方案的复杂性
  • 必须确保ThreadLocal在请求结束后清理,防止内存泄漏
  • 该自定义DataSource可被JPA/Hibernate自动使用,无需修改DatasourceConnectionProviderImpl

内容的提问来源于stack exchange,提问作者Mark R

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 20:10:30