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
相关产品推荐
相关产品推荐

